]> git.tdb.fi Git - ext/openal.git/blob - alc/backends/sndio.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / alc / backends / sndio.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 "sndio.h"
24
25 #include <functional>
26 #include <inttypes.h>
27 #include <poll.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <thread>
32
33 #include "alnumeric.h"
34 #include "core/device.h"
35 #include "core/helpers.h"
36 #include "core/logging.h"
37 #include "ringbuffer.h"
38 #include "threads.h"
39 #include "vector.h"
40
41 #include <sndio.h>
42
43
44 namespace {
45
46 static const char sndio_device[] = "SndIO Default";
47
48 struct SioPar : public sio_par {
49     SioPar() { sio_initpar(this); }
50
51     void clear() { sio_initpar(this); }
52 };
53
54 struct SndioPlayback final : public BackendBase {
55     SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
56     ~SndioPlayback() override;
57
58     int mixerProc();
59
60     void open(const char *name) override;
61     bool reset() override;
62     void start() override;
63     void stop() override;
64
65     sio_hdl *mSndHandle{nullptr};
66     uint mFrameStep{};
67
68     al::vector<al::byte> mBuffer;
69
70     std::atomic<bool> mKillNow{true};
71     std::thread mThread;
72
73     DEF_NEWDEL(SndioPlayback)
74 };
75
76 SndioPlayback::~SndioPlayback()
77 {
78     if(mSndHandle)
79         sio_close(mSndHandle);
80     mSndHandle = nullptr;
81 }
82
83 int SndioPlayback::mixerProc()
84 {
85     const size_t frameStep{mFrameStep};
86     const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
87
88     SetRTPriority();
89     althrd_setname(MIXER_THREAD_NAME);
90
91     while(!mKillNow.load(std::memory_order_acquire)
92         && mDevice->Connected.load(std::memory_order_acquire))
93     {
94         al::span<al::byte> buffer{mBuffer};
95
96         mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize),
97             frameStep);
98         while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
99         {
100             size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
101             if(wrote > buffer.size() || wrote == 0)
102             {
103                 ERR("sio_write failed: 0x%" PRIx64 "\n", wrote);
104                 mDevice->handleDisconnect("Failed to write playback samples");
105                 break;
106             }
107             buffer = buffer.subspan(wrote);
108         }
109     }
110
111     return 0;
112 }
113
114
115 void SndioPlayback::open(const char *name)
116 {
117     if(!name)
118         name = sndio_device;
119     else if(strcmp(name, sndio_device) != 0)
120         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
121             name};
122
123     sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
124     if(!sndHandle)
125         throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
126
127     if(mSndHandle)
128         sio_close(mSndHandle);
129     mSndHandle = sndHandle;
130
131     mDevice->DeviceName = name;
132 }
133
134 bool SndioPlayback::reset()
135 {
136     SioPar par;
137
138     auto tryfmt = mDevice->FmtType;
139 retry_params:
140     switch(tryfmt)
141     {
142     case DevFmtByte:
143         par.bits = 8;
144         par.sig = 1;
145         break;
146     case DevFmtUByte:
147         par.bits = 8;
148         par.sig = 0;
149         break;
150     case DevFmtShort:
151         par.bits = 16;
152         par.sig = 1;
153         break;
154     case DevFmtUShort:
155         par.bits = 16;
156         par.sig = 0;
157         break;
158     case DevFmtFloat:
159     case DevFmtInt:
160         par.bits = 32;
161         par.sig = 1;
162         break;
163     case DevFmtUInt:
164         par.bits = 32;
165         par.sig = 0;
166         break;
167     }
168     par.bps = SIO_BPS(par.bits);
169     par.le = SIO_LE_NATIVE;
170     par.msb = 1;
171
172     par.rate = mDevice->Frequency;
173     par.pchan = mDevice->channelsFromFmt();
174
175     par.round = mDevice->UpdateSize;
176     par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
177     if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
178
179     try {
180         if(!sio_setpar(mSndHandle, &par))
181             throw al::backend_exception{al::backend_error::DeviceError,
182                 "Failed to set device parameters"};
183
184         par.clear();
185         if(!sio_getpar(mSndHandle, &par))
186             throw al::backend_exception{al::backend_error::DeviceError,
187                 "Failed to get device parameters"};
188
189         if(par.bps > 1 && par.le != SIO_LE_NATIVE)
190             throw al::backend_exception{al::backend_error::DeviceError,
191                 "%s-endian samples not supported", par.le ? "Little" : "Big"};
192         if(par.bits < par.bps*8 && !par.msb)
193             throw al::backend_exception{al::backend_error::DeviceError,
194                 "MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
195         if(par.pchan < 1)
196             throw al::backend_exception{al::backend_error::DeviceError,
197                 "No playback channels on device"};
198     }
199     catch(al::backend_exception &e) {
200         if(tryfmt == DevFmtShort)
201             throw;
202         par.clear();
203         tryfmt = DevFmtShort;
204         goto retry_params;
205     }
206
207     if(par.bps == 1)
208         mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte;
209     else if(par.bps == 2)
210         mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort;
211     else if(par.bps == 4)
212         mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt;
213     else
214         throw al::backend_exception{al::backend_error::DeviceError,
215             "Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8};
216
217     mFrameStep = par.pchan;
218     if(par.pchan != mDevice->channelsFromFmt())
219     {
220         WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s",
221             DevFmtChannelsString(mDevice->FmtChans));
222         if(par.pchan < 2) mDevice->FmtChans = DevFmtMono;
223         else mDevice->FmtChans = DevFmtStereo;
224     }
225     mDevice->Frequency = par.rate;
226
227     setDefaultChannelOrder();
228
229     mDevice->UpdateSize = par.round;
230     mDevice->BufferSize = par.bufsz + par.round;
231
232     mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps);
233     if(par.sig == 1)
234         std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
235     else if(par.bits == 8)
236         std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80));
237     else if(par.bits == 16)
238         std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000);
239     else if(par.bits == 32)
240         std::fill_n(reinterpret_cast<uint32_t*>(mBuffer.data()), mBuffer.size()/4, 0x80000000u);
241
242     return true;
243 }
244
245 void SndioPlayback::start()
246 {
247     if(!sio_start(mSndHandle))
248         throw al::backend_exception{al::backend_error::DeviceError, "Error starting playback"};
249
250     try {
251         mKillNow.store(false, std::memory_order_release);
252         mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this};
253     }
254     catch(std::exception& e) {
255         sio_stop(mSndHandle);
256         throw al::backend_exception{al::backend_error::DeviceError,
257             "Failed to start mixing thread: %s", e.what()};
258     }
259 }
260
261 void SndioPlayback::stop()
262 {
263     if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
264         return;
265     mThread.join();
266
267     if(!sio_stop(mSndHandle))
268         ERR("Error stopping device\n");
269 }
270
271
272 /* TODO: This could be improved by avoiding the ring buffer and record thread,
273  * counting the available samples with the sio_onmove callback and reading
274  * directly from the device. However, this depends on reasonable support for
275  * capture buffer sizes apps may request.
276  */
277 struct SndioCapture final : public BackendBase {
278     SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
279     ~SndioCapture() override;
280
281     int recordProc();
282
283     void open(const char *name) override;
284     void start() override;
285     void stop() override;
286     void captureSamples(al::byte *buffer, uint samples) override;
287     uint availableSamples() override;
288
289     sio_hdl *mSndHandle{nullptr};
290
291     RingBufferPtr mRing;
292
293     std::atomic<bool> mKillNow{true};
294     std::thread mThread;
295
296     DEF_NEWDEL(SndioCapture)
297 };
298
299 SndioCapture::~SndioCapture()
300 {
301     if(mSndHandle)
302         sio_close(mSndHandle);
303     mSndHandle = nullptr;
304 }
305
306 int SndioCapture::recordProc()
307 {
308     SetRTPriority();
309     althrd_setname(RECORD_THREAD_NAME);
310
311     const uint frameSize{mDevice->frameSizeFromFmt()};
312
313     int nfds_pre{sio_nfds(mSndHandle)};
314     if(nfds_pre <= 0)
315     {
316         mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre);
317         return 1;
318     }
319
320     auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre));
321
322     while(!mKillNow.load(std::memory_order_acquire)
323         && mDevice->Connected.load(std::memory_order_acquire))
324     {
325         /* Wait until there's some samples to read. */
326         const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
327         if(nfds <= 0)
328         {
329             mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
330             break;
331         }
332         int pollres{::poll(fds.get(), static_cast<uint>(nfds), 2000)};
333         if(pollres < 0)
334         {
335             if(errno == EINTR) continue;
336             mDevice->handleDisconnect("Poll error: %s", strerror(errno));
337             break;
338         }
339         if(pollres == 0)
340             continue;
341
342         const int revents{sio_revents(mSndHandle, fds.get())};
343         if((revents&POLLHUP))
344         {
345             mDevice->handleDisconnect("Got POLLHUP from poll events");
346             break;
347         }
348         if(!(revents&POLLIN))
349             continue;
350
351         auto data = mRing->getWriteVector();
352         al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
353         while(!buffer.empty())
354         {
355             size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
356             if(got == 0)
357                 break;
358             if(got > buffer.size())
359             {
360                 ERR("sio_read failed: 0x%" PRIx64 "\n", got);
361                 mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got);
362                 break;
363             }
364
365             mRing->writeAdvance(got / frameSize);
366             buffer = buffer.subspan(got);
367             if(buffer.empty())
368             {
369                 data = mRing->getWriteVector();
370                 buffer = {data.first.buf, data.first.len*frameSize};
371             }
372         }
373         if(buffer.empty())
374         {
375             /* Got samples to read, but no place to store it. Drop it. */
376             static char junk[4096];
377             sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize));
378         }
379     }
380
381     return 0;
382 }
383
384
385 void SndioCapture::open(const char *name)
386 {
387     if(!name)
388         name = sndio_device;
389     else if(strcmp(name, sndio_device) != 0)
390         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
391             name};
392
393     mSndHandle = sio_open(nullptr, SIO_REC, true);
394     if(mSndHandle == nullptr)
395         throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
396
397     SioPar par;
398     switch(mDevice->FmtType)
399     {
400     case DevFmtByte:
401         par.bits = 8;
402         par.sig = 1;
403         break;
404     case DevFmtUByte:
405         par.bits = 8;
406         par.sig = 0;
407         break;
408     case DevFmtShort:
409         par.bits = 16;
410         par.sig = 1;
411         break;
412     case DevFmtUShort:
413         par.bits = 16;
414         par.sig = 0;
415         break;
416     case DevFmtInt:
417         par.bits = 32;
418         par.sig = 1;
419         break;
420     case DevFmtUInt:
421         par.bits = 32;
422         par.sig = 0;
423         break;
424     case DevFmtFloat:
425         throw al::backend_exception{al::backend_error::DeviceError,
426             "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
427     }
428     par.bps = SIO_BPS(par.bits);
429     par.le = SIO_LE_NATIVE;
430     par.msb = 1;
431     par.rchan = mDevice->channelsFromFmt();
432     par.rate = mDevice->Frequency;
433
434     par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
435     par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
436
437     if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
438         throw al::backend_exception{al::backend_error::DeviceError,
439             "Failed to set device praameters"};
440
441     if(par.bps > 1 && par.le != SIO_LE_NATIVE)
442         throw al::backend_exception{al::backend_error::DeviceError,
443             "%s-endian samples not supported", par.le ? "Little" : "Big"};
444     if(par.bits < par.bps*8 && !par.msb)
445         throw al::backend_exception{al::backend_error::DeviceError,
446             "Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
447
448     auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool
449     {
450         return (fmttype == DevFmtByte && p.bps == 1 && p.sig != 0)
451             || (fmttype == DevFmtUByte && p.bps == 1 && p.sig == 0)
452             || (fmttype == DevFmtShort && p.bps == 2 && p.sig != 0)
453             || (fmttype == DevFmtUShort && p.bps == 2 && p.sig == 0)
454             || (fmttype == DevFmtInt && p.bps == 4 && p.sig != 0)
455             || (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0);
456     };
457     if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan
458         || mDevice->Frequency != par.rate)
459         throw al::backend_exception{al::backend_error::DeviceError,
460             "Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead",
461             DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
462             mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
463
464     mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
465     mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
466     mDevice->UpdateSize = par.round;
467
468     setDefaultChannelOrder();
469
470     mDevice->DeviceName = name;
471 }
472
473 void SndioCapture::start()
474 {
475     if(!sio_start(mSndHandle))
476         throw al::backend_exception{al::backend_error::DeviceError, "Error starting capture"};
477
478     try {
479         mKillNow.store(false, std::memory_order_release);
480         mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this};
481     }
482     catch(std::exception& e) {
483         sio_stop(mSndHandle);
484         throw al::backend_exception{al::backend_error::DeviceError,
485             "Failed to start capture thread: %s", e.what()};
486     }
487 }
488
489 void SndioCapture::stop()
490 {
491     if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
492         return;
493     mThread.join();
494
495     if(!sio_stop(mSndHandle))
496         ERR("Error stopping device\n");
497 }
498
499 void SndioCapture::captureSamples(al::byte *buffer, uint samples)
500 { mRing->read(buffer, samples); }
501
502 uint SndioCapture::availableSamples()
503 { return static_cast<uint>(mRing->readSpace()); }
504
505 } // namespace
506
507 BackendFactory &SndIOBackendFactory::getFactory()
508 {
509     static SndIOBackendFactory factory{};
510     return factory;
511 }
512
513 bool SndIOBackendFactory::init()
514 { return true; }
515
516 bool SndIOBackendFactory::querySupport(BackendType type)
517 { return (type == BackendType::Playback || type == BackendType::Capture); }
518
519 std::string SndIOBackendFactory::probe(BackendType type)
520 {
521     std::string outnames;
522     switch(type)
523     {
524     case BackendType::Playback:
525     case BackendType::Capture:
526         /* Includes null char. */
527         outnames.append(sndio_device, sizeof(sndio_device));
528         break;
529     }
530     return outnames;
531 }
532
533 BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)
534 {
535     if(type == BackendType::Playback)
536         return BackendPtr{new SndioPlayback{device}};
537     if(type == BackendType::Capture)
538         return BackendPtr{new SndioCapture{device}};
539     return nullptr;
540 }