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
41 #include "alnumeric.h"
42 #include "core/device.h"
43 #include "core/helpers.h"
44 #include "core/logging.h"
45 #include "ringbuffer.h"
49 #ifndef WAVE_FORMAT_IEEE_FLOAT
50 #define WAVE_FORMAT_IEEE_FLOAT 0x0003
55 #define DEVNAME_HEAD "OpenAL Soft on "
58 al::vector<std::string> PlaybackDevices;
59 al::vector<std::string> CaptureDevices;
61 bool checkName(const al::vector<std::string> &list, const std::string &name)
62 { return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
64 void ProbePlaybackDevices(void)
66 PlaybackDevices.clear();
68 UINT numdevs{waveOutGetNumDevs()};
69 PlaybackDevices.reserve(numdevs);
70 for(UINT i{0};i < numdevs;++i)
74 WAVEOUTCAPSW WaveCaps{};
75 if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
77 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
80 std::string newname{basename};
81 while(checkName(PlaybackDevices, newname))
85 newname += std::to_string(++count);
87 dname = std::move(newname);
89 TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
91 PlaybackDevices.emplace_back(std::move(dname));
95 void ProbeCaptureDevices(void)
97 CaptureDevices.clear();
99 UINT numdevs{waveInGetNumDevs()};
100 CaptureDevices.reserve(numdevs);
101 for(UINT i{0};i < numdevs;++i)
105 WAVEINCAPSW WaveCaps{};
106 if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
108 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
111 std::string newname{basename};
112 while(checkName(CaptureDevices, newname))
116 newname += std::to_string(++count);
118 dname = std::move(newname);
120 TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
122 CaptureDevices.emplace_back(std::move(dname));
127 struct WinMMPlayback final : public BackendBase {
128 WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
129 ~WinMMPlayback() override;
131 void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
132 static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
133 { reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); }
137 void open(const char *name) override;
138 bool reset() override;
139 void start() override;
140 void stop() override;
142 std::atomic<uint> mWritable{0u};
145 std::array<WAVEHDR,4> mWaveBuffer{};
147 HWAVEOUT mOutHdl{nullptr};
149 WAVEFORMATEX mFormat{};
151 std::atomic<bool> mKillNow{true};
154 DEF_NEWDEL(WinMMPlayback)
157 WinMMPlayback::~WinMMPlayback()
160 waveOutClose(mOutHdl);
163 al_free(mWaveBuffer[0].lpData);
164 std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
167 /* WinMMPlayback::waveOutProc
169 * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
170 * completed and returns to the application (for more data)
172 void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
174 if(msg != WOM_DONE) return;
175 mWritable.fetch_add(1, std::memory_order_acq_rel);
179 FORCE_ALIGN int WinMMPlayback::mixerProc()
182 althrd_setname(MIXER_THREAD_NAME);
184 while(!mKillNow.load(std::memory_order_acquire)
185 && mDevice->Connected.load(std::memory_order_acquire))
187 uint todo{mWritable.load(std::memory_order_acquire)};
196 WAVEHDR &waveHdr = mWaveBuffer[widx];
197 if(++widx == mWaveBuffer.size()) widx = 0;
199 mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels);
200 mWritable.fetch_sub(1, std::memory_order_acq_rel);
201 waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
203 mIdx = static_cast<uint>(widx);
210 void WinMMPlayback::open(const char *name)
212 if(PlaybackDevices.empty())
213 ProbePlaybackDevices();
215 // Find the Device ID matching the deviceName if valid
217 std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
218 PlaybackDevices.cbegin();
219 if(iter == PlaybackDevices.cend())
220 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
222 auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
224 DevFmtType fmttype{mDevice->FmtType};
226 WAVEFORMATEX format{};
227 if(fmttype == DevFmtFloat)
229 format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
230 format.wBitsPerSample = 32;
234 format.wFormatTag = WAVE_FORMAT_PCM;
235 if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
236 format.wBitsPerSample = 8;
238 format.wBitsPerSample = 16;
240 format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
241 format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
242 format.nSamplesPerSec = mDevice->Frequency;
243 format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
246 HWAVEOUT outHandle{};
247 MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format,
248 reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
249 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
250 if(res != MMSYSERR_NOERROR)
252 if(fmttype == DevFmtFloat)
254 fmttype = DevFmtShort;
257 throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
261 waveOutClose(mOutHdl);
265 mDevice->DeviceName = PlaybackDevices[DeviceID];
268 bool WinMMPlayback::reset()
270 mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
271 mFormat.nSamplesPerSec / mDevice->Frequency);
272 mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u;
273 mDevice->UpdateSize = mDevice->BufferSize / 4;
274 mDevice->Frequency = mFormat.nSamplesPerSec;
276 if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
278 if(mFormat.wBitsPerSample == 32)
279 mDevice->FmtType = DevFmtFloat;
282 ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
286 else if(mFormat.wFormatTag == WAVE_FORMAT_PCM)
288 if(mFormat.wBitsPerSample == 16)
289 mDevice->FmtType = DevFmtShort;
290 else if(mFormat.wBitsPerSample == 8)
291 mDevice->FmtType = DevFmtUByte;
294 ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
300 ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
304 if(mFormat.nChannels >= 2)
305 mDevice->FmtChans = DevFmtStereo;
306 else if(mFormat.nChannels == 1)
307 mDevice->FmtChans = DevFmtMono;
310 ERR("Unhandled channel count: %d\n", mFormat.nChannels);
313 setDefaultWFXChannelOrder();
315 uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
317 al_free(mWaveBuffer[0].lpData);
318 mWaveBuffer[0] = WAVEHDR{};
319 mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
320 mWaveBuffer[0].dwBufferLength = BufferSize;
321 for(size_t i{1};i < mWaveBuffer.size();i++)
323 mWaveBuffer[i] = WAVEHDR{};
324 mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
325 mWaveBuffer[i].dwBufferLength = BufferSize;
332 void WinMMPlayback::start()
335 for(auto &waveHdr : mWaveBuffer)
336 waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR));
337 mWritable.store(static_cast<uint>(mWaveBuffer.size()), std::memory_order_release);
339 mKillNow.store(false, std::memory_order_release);
340 mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
342 catch(std::exception& e) {
343 throw al::backend_exception{al::backend_error::DeviceError,
344 "Failed to start mixing thread: %s", e.what()};
348 void WinMMPlayback::stop()
350 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
354 while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
356 for(auto &waveHdr : mWaveBuffer)
357 waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR));
358 mWritable.store(0, std::memory_order_release);
362 struct WinMMCapture final : public BackendBase {
363 WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { }
364 ~WinMMCapture() override;
366 void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
367 static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
368 { reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); }
372 void open(const char *name) override;
373 void start() override;
374 void stop() override;
375 void captureSamples(al::byte *buffer, uint samples) override;
376 uint availableSamples() override;
378 std::atomic<uint> mReadable{0u};
381 std::array<WAVEHDR,4> mWaveBuffer{};
383 HWAVEIN mInHdl{nullptr};
385 RingBufferPtr mRing{nullptr};
387 WAVEFORMATEX mFormat{};
389 std::atomic<bool> mKillNow{true};
392 DEF_NEWDEL(WinMMCapture)
395 WinMMCapture::~WinMMCapture()
397 // Close the Wave device
402 al_free(mWaveBuffer[0].lpData);
403 std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
406 /* WinMMCapture::waveInProc
408 * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
409 * completed and returns to the application (with more data).
411 void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
413 if(msg != WIM_DATA) return;
414 mReadable.fetch_add(1, std::memory_order_acq_rel);
418 int WinMMCapture::captureProc()
420 althrd_setname(RECORD_THREAD_NAME);
422 while(!mKillNow.load(std::memory_order_acquire) &&
423 mDevice->Connected.load(std::memory_order_acquire))
425 uint todo{mReadable.load(std::memory_order_acquire)};
434 WAVEHDR &waveHdr = mWaveBuffer[widx];
435 widx = (widx+1) % mWaveBuffer.size();
437 mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
438 mReadable.fetch_sub(1, std::memory_order_acq_rel);
439 waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
441 mIdx = static_cast<uint>(widx);
448 void WinMMCapture::open(const char *name)
450 if(CaptureDevices.empty())
451 ProbeCaptureDevices();
453 // Find the Device ID matching the deviceName if valid
455 std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
456 CaptureDevices.cbegin();
457 if(iter == CaptureDevices.cend())
458 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
460 auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
462 switch(mDevice->FmtChans)
475 throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
476 DevFmtChannelsString(mDevice->FmtChans)};
479 switch(mDevice->FmtType)
490 throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
491 DevFmtTypeString(mDevice->FmtType)};
494 mFormat = WAVEFORMATEX{};
495 mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ?
496 WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
497 mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
498 mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
499 mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
500 mFormat.nSamplesPerSec = mDevice->Frequency;
501 mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
504 MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat,
505 reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
506 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
507 if(res != MMSYSERR_NOERROR)
508 throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res};
510 // Ensure each buffer is 50ms each
511 DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
512 BufferSize -= (BufferSize % mFormat.nBlockAlign);
514 // Allocate circular memory buffer for the captured audio
515 // Make sure circular buffer is at least 100ms in size
516 uint CapturedDataSize{mDevice->BufferSize};
517 CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
519 mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
521 al_free(mWaveBuffer[0].lpData);
522 mWaveBuffer[0] = WAVEHDR{};
523 mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
524 mWaveBuffer[0].dwBufferLength = BufferSize;
525 for(size_t i{1};i < mWaveBuffer.size();++i)
527 mWaveBuffer[i] = WAVEHDR{};
528 mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
529 mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
532 mDevice->DeviceName = CaptureDevices[DeviceID];
535 void WinMMCapture::start()
538 for(size_t i{0};i < mWaveBuffer.size();++i)
540 waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
541 waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
544 mKillNow.store(false, std::memory_order_release);
545 mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
549 catch(std::exception& e) {
550 throw al::backend_exception{al::backend_error::DeviceError,
551 "Failed to start recording thread: %s", e.what()};
555 void WinMMCapture::stop()
559 mKillNow.store(true, std::memory_order_release);
560 if(mThread.joinable())
567 for(size_t i{0};i < mWaveBuffer.size();++i)
568 waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
570 mReadable.store(0, std::memory_order_release);
574 void WinMMCapture::captureSamples(al::byte *buffer, uint samples)
575 { mRing->read(buffer, samples); }
577 uint WinMMCapture::availableSamples()
578 { return static_cast<uint>(mRing->readSpace()); }
583 bool WinMMBackendFactory::init()
586 bool WinMMBackendFactory::querySupport(BackendType type)
587 { return type == BackendType::Playback || type == BackendType::Capture; }
589 std::string WinMMBackendFactory::probe(BackendType type)
591 std::string outnames;
592 auto add_device = [&outnames](const std::string &dname) -> void
594 /* +1 to also append the null char (to ensure a null-separated list and
595 * double-null terminated list).
598 outnames.append(dname.c_str(), dname.length()+1);
602 case BackendType::Playback:
603 ProbePlaybackDevices();
604 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
607 case BackendType::Capture:
608 ProbeCaptureDevices();
609 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
615 BackendPtr WinMMBackendFactory::createBackend(DeviceBase *device, BackendType type)
617 if(type == BackendType::Playback)
618 return BackendPtr{new WinMMPlayback{device}};
619 if(type == BackendType::Capture)
620 return BackendPtr{new WinMMCapture{device}};
624 BackendFactory &WinMMBackendFactory::getFactory()
626 static WinMMBackendFactory factory{};