]> git.tdb.fi Git - ext/openal.git/blob - alc/backends/portaudio.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / alc / backends / portaudio.cpp
1 /**
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.
8  *
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.
13  *
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
19  */
20
21 #include "config.h"
22
23 #include "portaudio.h"
24
25 #include <cstdio>
26 #include <cstdlib>
27 #include <cstring>
28
29 #include "alc/alconfig.h"
30 #include "alnumeric.h"
31 #include "core/device.h"
32 #include "core/logging.h"
33 #include "dynload.h"
34 #include "ringbuffer.h"
35
36 #include <portaudio.h>
37
38
39 namespace {
40
41 constexpr char pa_device[] = "PortAudio Default";
42
43
44 #ifdef HAVE_DYNLOAD
45 void *pa_handle;
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);
57 #undef MAKE_FUNC
58
59 #ifndef IN_IDE_PARSER
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
70 #endif
71 #endif
72
73
74 struct PortPlayback final : public BackendBase {
75     PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
76     ~PortPlayback() override;
77
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
83     {
84         return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
85             framesPerBuffer, timeInfo, statusFlags);
86     }
87
88     void open(const char *name) override;
89     bool reset() override;
90     void start() override;
91     void stop() override;
92
93     PaStream *mStream{nullptr};
94     PaStreamParameters mParams{};
95     uint mUpdateSize{0u};
96
97     DEF_NEWDEL(PortPlayback)
98 };
99
100 PortPlayback::~PortPlayback()
101 {
102     PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
103     if(err != paNoError)
104         ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
105     mStream = nullptr;
106 }
107
108
109 int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer,
110     const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
111 {
112     mDevice->renderSamples(outputBuffer, static_cast<uint>(framesPerBuffer),
113         static_cast<uint>(mParams.channelCount));
114     return 0;
115 }
116
117
118 void PortPlayback::open(const char *name)
119 {
120     if(!name)
121         name = pa_device;
122     else if(strcmp(name, pa_device) != 0)
123         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
124             name};
125
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;
132
133     params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
134
135     switch(mDevice->FmtType)
136     {
137     case DevFmtByte:
138         params.sampleFormat = paInt8;
139         break;
140     case DevFmtUByte:
141         params.sampleFormat = paUInt8;
142         break;
143     case DevFmtUShort:
144         /* fall-through */
145     case DevFmtShort:
146         params.sampleFormat = paInt16;
147         break;
148     case DevFmtUInt:
149         /* fall-through */
150     case DevFmtInt:
151         params.sampleFormat = paInt32;
152         break;
153     case DevFmtFloat:
154         params.sampleFormat = paFloat32;
155         break;
156     }
157
158 retry_open:
159     PaStream *stream{};
160     PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency, mDevice->UpdateSize,
161         paNoFlag, &PortPlayback::writeCallbackC, this)};
162     if(err != paNoError)
163     {
164         if(params.sampleFormat == paFloat32)
165         {
166             params.sampleFormat = paInt16;
167             goto retry_open;
168         }
169         throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
170             Pa_GetErrorText(err)};
171     }
172
173     Pa_CloseStream(mStream);
174     mStream = stream;
175     mParams = params;
176     mUpdateSize = mDevice->UpdateSize;
177
178     mDevice->DeviceName = name;
179 }
180
181 bool PortPlayback::reset()
182 {
183     const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
184     mDevice->Frequency = static_cast<uint>(streamInfo->sampleRate);
185     mDevice->UpdateSize = mUpdateSize;
186
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;
197     else
198     {
199         ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
200         return false;
201     }
202
203     if(mParams.channelCount >= 2)
204         mDevice->FmtChans = DevFmtStereo;
205     else if(mParams.channelCount == 1)
206         mDevice->FmtChans = DevFmtMono;
207     else
208     {
209         ERR("Unexpected channel count: %u\n", mParams.channelCount);
210         return false;
211     }
212     setDefaultChannelOrder();
213
214     return true;
215 }
216
217 void PortPlayback::start()
218 {
219     const PaError err{Pa_StartStream(mStream)};
220     if(err == paNoError)
221         throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
222             Pa_GetErrorText(err)};
223 }
224
225 void PortPlayback::stop()
226 {
227     PaError err{Pa_StopStream(mStream)};
228     if(err != paNoError)
229         ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
230 }
231
232
233 struct PortCapture final : public BackendBase {
234     PortCapture(DeviceBase *device) noexcept : BackendBase{device} { }
235     ~PortCapture() override;
236
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
242     {
243         return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
244             framesPerBuffer, timeInfo, statusFlags);
245     }
246
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;
252
253     PaStream *mStream{nullptr};
254     PaStreamParameters mParams;
255
256     RingBufferPtr mRing{nullptr};
257
258     DEF_NEWDEL(PortCapture)
259 };
260
261 PortCapture::~PortCapture()
262 {
263     PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
264     if(err != paNoError)
265         ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
266     mStream = nullptr;
267 }
268
269
270 int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
271     const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
272 {
273     mRing->write(inputBuffer, framesPerBuffer);
274     return 0;
275 }
276
277
278 void PortCapture::open(const char *name)
279 {
280     if(!name)
281         name = pa_device;
282     else if(strcmp(name, pa_device) != 0)
283         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
284             name};
285
286     uint samples{mDevice->BufferSize};
287     samples = maxu(samples, 100 * mDevice->Frequency / 1000);
288     uint frame_size{mDevice->frameSizeFromFmt()};
289
290     mRing = RingBuffer::Create(samples, frame_size, false);
291
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;
297
298     switch(mDevice->FmtType)
299     {
300     case DevFmtByte:
301         mParams.sampleFormat = paInt8;
302         break;
303     case DevFmtUByte:
304         mParams.sampleFormat = paUInt8;
305         break;
306     case DevFmtShort:
307         mParams.sampleFormat = paInt16;
308         break;
309     case DevFmtInt:
310         mParams.sampleFormat = paInt32;
311         break;
312     case DevFmtFloat:
313         mParams.sampleFormat = paFloat32;
314         break;
315     case DevFmtUInt:
316     case DevFmtUShort:
317         throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
318             DevFmtTypeString(mDevice->FmtType)};
319     }
320     mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
321
322     PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
323         paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
324     if(err != paNoError)
325         throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
326             Pa_GetErrorText(err)};
327
328     mDevice->DeviceName = name;
329 }
330
331
332 void PortCapture::start()
333 {
334     const PaError err{Pa_StartStream(mStream)};
335     if(err != paNoError)
336         throw al::backend_exception{al::backend_error::DeviceError,
337             "Failed to start recording: %s", Pa_GetErrorText(err)};
338 }
339
340 void PortCapture::stop()
341 {
342     PaError err{Pa_StopStream(mStream)};
343     if(err != paNoError)
344         ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
345 }
346
347
348 uint PortCapture::availableSamples()
349 { return static_cast<uint>(mRing->readSpace()); }
350
351 void PortCapture::captureSamples(al::byte *buffer, uint samples)
352 { mRing->read(buffer, samples); }
353
354 } // namespace
355
356
357 bool PortBackendFactory::init()
358 {
359     PaError err;
360
361 #ifdef HAVE_DYNLOAD
362     if(!pa_handle)
363     {
364 #ifdef _WIN32
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"
370 #else
371 # define PALIB "libportaudio.so.2"
372 #endif
373
374         pa_handle = LoadLib(PALIB);
375         if(!pa_handle)
376             return false;
377
378 #define LOAD_FUNC(f) do {                                                     \
379     p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f));        \
380     if(p##f == nullptr)                                                       \
381     {                                                                         \
382         CloseLib(pa_handle);                                                  \
383         pa_handle = nullptr;                                                  \
384         return false;                                                         \
385     }                                                                         \
386 } while(0)
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);
397 #undef LOAD_FUNC
398
399         if((err=Pa_Initialize()) != paNoError)
400         {
401             ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
402             CloseLib(pa_handle);
403             pa_handle = nullptr;
404             return false;
405         }
406     }
407 #else
408     if((err=Pa_Initialize()) != paNoError)
409     {
410         ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
411         return false;
412     }
413 #endif
414     return true;
415 }
416
417 bool PortBackendFactory::querySupport(BackendType type)
418 { return (type == BackendType::Playback || type == BackendType::Capture); }
419
420 std::string PortBackendFactory::probe(BackendType type)
421 {
422     std::string outnames;
423     switch(type)
424     {
425     case BackendType::Playback:
426     case BackendType::Capture:
427         /* Includes null char. */
428         outnames.append(pa_device, sizeof(pa_device));
429         break;
430     }
431     return outnames;
432 }
433
434 BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)
435 {
436     if(type == BackendType::Playback)
437         return BackendPtr{new PortPlayback{device}};
438     if(type == BackendType::Capture)
439         return BackendPtr{new PortCapture{device}};
440     return nullptr;
441 }
442
443 BackendFactory &PortBackendFactory::getFactory()
444 {
445     static PortBackendFactory factory{};
446     return factory;
447 }