Add "fast" variants for the bsinc resamplers
[openal-soft.git] / alc / backends / winmm.cpp
blobd58fa959e436ce3ab9b21f08476d0daccf39d488
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.
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
21 #include "config.h"
23 #include "backends/winmm.h"
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <memory.h>
29 #include <windows.h>
30 #include <mmsystem.h>
32 #include <array>
33 #include <atomic>
34 #include <thread>
35 #include <vector>
36 #include <string>
37 #include <algorithm>
38 #include <functional>
40 #include "alcmain.h"
41 #include "alu.h"
42 #include "ringbuffer.h"
43 #include "strutils.h"
44 #include "threads.h"
45 #include "compat.h"
47 #ifndef WAVE_FORMAT_IEEE_FLOAT
48 #define WAVE_FORMAT_IEEE_FLOAT 0x0003
49 #endif
51 namespace {
53 #define DEVNAME_HEAD "OpenAL Soft on "
56 al::vector<std::string> PlaybackDevices;
57 al::vector<std::string> CaptureDevices;
59 bool checkName(const al::vector<std::string> &list, const std::string &name)
60 { return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
62 void ProbePlaybackDevices(void)
64 PlaybackDevices.clear();
66 ALuint numdevs{waveOutGetNumDevs()};
67 PlaybackDevices.reserve(numdevs);
68 for(ALuint i{0};i < numdevs;i++)
70 std::string dname;
72 WAVEOUTCAPSW WaveCaps{};
73 if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
75 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
77 int count{1};
78 std::string newname{basename};
79 while(checkName(PlaybackDevices, newname))
81 newname = basename;
82 newname += " #";
83 newname += std::to_string(++count);
85 dname = std::move(newname);
87 TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
89 PlaybackDevices.emplace_back(std::move(dname));
93 void ProbeCaptureDevices(void)
95 CaptureDevices.clear();
97 ALuint numdevs{waveInGetNumDevs()};
98 CaptureDevices.reserve(numdevs);
99 for(ALuint i{0};i < numdevs;i++)
101 std::string dname;
103 WAVEINCAPSW WaveCaps{};
104 if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
106 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
108 int count{1};
109 std::string newname{basename};
110 while(checkName(CaptureDevices, newname))
112 newname = basename;
113 newname += " #";
114 newname += std::to_string(++count);
116 dname = std::move(newname);
118 TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
120 CaptureDevices.emplace_back(std::move(dname));
125 struct WinMMPlayback final : public BackendBase {
126 WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
127 ~WinMMPlayback() override;
129 static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2);
130 void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2);
132 int mixerProc();
134 ALCenum open(const ALCchar *name) override;
135 bool reset() override;
136 bool start() override;
137 void stop() override;
139 std::atomic<ALuint> mWritable{0u};
140 al::semaphore mSem;
141 ALuint mIdx{0u};
142 std::array<WAVEHDR,4> mWaveBuffer{};
144 HWAVEOUT mOutHdl{nullptr};
146 WAVEFORMATEX mFormat{};
148 std::atomic<bool> mKillNow{true};
149 std::thread mThread;
151 DEF_NEWDEL(WinMMPlayback)
154 WinMMPlayback::~WinMMPlayback()
156 if(mOutHdl)
157 waveOutClose(mOutHdl);
158 mOutHdl = nullptr;
160 al_free(mWaveBuffer[0].lpData);
161 std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
165 void CALLBACK WinMMPlayback::waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2)
166 { reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); }
168 /* WinMMPlayback::waveOutProc
170 * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
171 * completed and returns to the application (for more data)
173 void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR)
175 if(msg != WOM_DONE) return;
176 mWritable.fetch_add(1, std::memory_order_acq_rel);
177 mSem.post();
180 FORCE_ALIGN int WinMMPlayback::mixerProc()
182 SetRTPriority();
183 althrd_setname(MIXER_THREAD_NAME);
185 lock();
186 while(!mKillNow.load(std::memory_order_acquire) &&
187 mDevice->Connected.load(std::memory_order_acquire))
189 ALsizei todo = mWritable.load(std::memory_order_acquire);
190 if(todo < 1)
192 unlock();
193 mSem.wait();
194 lock();
195 continue;
198 size_t widx{mIdx};
199 do {
200 WAVEHDR &waveHdr = mWaveBuffer[widx];
201 widx = (widx+1) % mWaveBuffer.size();
203 aluMixData(mDevice, waveHdr.lpData, mDevice->UpdateSize);
204 mWritable.fetch_sub(1, std::memory_order_acq_rel);
205 waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
206 } while(--todo);
207 mIdx = static_cast<ALuint>(widx);
209 unlock();
211 return 0;
215 ALCenum WinMMPlayback::open(const ALCchar *name)
217 if(PlaybackDevices.empty())
218 ProbePlaybackDevices();
220 // Find the Device ID matching the deviceName if valid
221 auto iter = name ?
222 std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
223 PlaybackDevices.cbegin();
224 if(iter == PlaybackDevices.cend()) return ALC_INVALID_VALUE;
225 auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
227 retry_open:
228 mFormat = WAVEFORMATEX{};
229 if(mDevice->FmtType == DevFmtFloat)
231 mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
232 mFormat.wBitsPerSample = 32;
234 else
236 mFormat.wFormatTag = WAVE_FORMAT_PCM;
237 if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte)
238 mFormat.wBitsPerSample = 8;
239 else
240 mFormat.wBitsPerSample = 16;
242 mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
243 mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
244 mFormat.nSamplesPerSec = mDevice->Frequency;
245 mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
246 mFormat.cbSize = 0;
248 MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat,
249 reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
250 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
251 if(res != MMSYSERR_NOERROR)
253 if(mDevice->FmtType == DevFmtFloat)
255 mDevice->FmtType = DevFmtShort;
256 goto retry_open;
258 ERR("waveOutOpen failed: %u\n", res);
259 return ALC_INVALID_VALUE;
262 mDevice->DeviceName = PlaybackDevices[DeviceID];
263 return ALC_NO_ERROR;
266 bool WinMMPlayback::reset()
268 mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
269 mFormat.nSamplesPerSec / mDevice->Frequency);
270 mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3;
271 mDevice->UpdateSize = mDevice->BufferSize / 4;
272 mDevice->Frequency = mFormat.nSamplesPerSec;
274 if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
276 if(mFormat.wBitsPerSample == 32)
277 mDevice->FmtType = DevFmtFloat;
278 else
280 ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
281 return false;
284 else if(mFormat.wFormatTag == WAVE_FORMAT_PCM)
286 if(mFormat.wBitsPerSample == 16)
287 mDevice->FmtType = DevFmtShort;
288 else if(mFormat.wBitsPerSample == 8)
289 mDevice->FmtType = DevFmtUByte;
290 else
292 ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
293 return false;
296 else
298 ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
299 return false;
302 if(mFormat.nChannels == 2)
303 mDevice->FmtChans = DevFmtStereo;
304 else if(mFormat.nChannels == 1)
305 mDevice->FmtChans = DevFmtMono;
306 else
308 ERR("Unhandled channel count: %d\n", mFormat.nChannels);
309 return false;
311 SetDefaultWFXChannelOrder(mDevice);
313 ALuint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()};
315 al_free(mWaveBuffer[0].lpData);
316 mWaveBuffer[0] = WAVEHDR{};
317 mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
318 mWaveBuffer[0].dwBufferLength = BufferSize;
319 for(size_t i{1};i < mWaveBuffer.size();i++)
321 mWaveBuffer[i] = WAVEHDR{};
322 mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
323 mWaveBuffer[i].dwBufferLength = BufferSize;
325 mIdx = 0;
327 return true;
330 bool WinMMPlayback::start()
332 try {
333 std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
334 [this](WAVEHDR &waveHdr) -> void
335 { waveOutPrepareHeader(mOutHdl, &waveHdr, static_cast<UINT>(sizeof(WAVEHDR))); }
337 mWritable.store(static_cast<ALuint>(mWaveBuffer.size()), std::memory_order_release);
339 mKillNow.store(false, std::memory_order_release);
340 mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
341 return true;
343 catch(std::exception& e) {
344 ERR("Failed to start mixing thread: %s\n", e.what());
346 catch(...) {
348 return false;
351 void WinMMPlayback::stop()
353 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
354 return;
355 mThread.join();
357 while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
358 mSem.wait();
359 std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
360 [this](WAVEHDR &waveHdr) -> void
361 { waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); }
363 mWritable.store(0, std::memory_order_release);
367 struct WinMMCapture final : public BackendBase {
368 WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { }
369 ~WinMMCapture() override;
371 static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2);
372 void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2);
374 int captureProc();
376 ALCenum open(const ALCchar *name) override;
377 bool start() override;
378 void stop() override;
379 ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
380 ALCuint availableSamples() override;
382 std::atomic<ALuint> mReadable{0u};
383 al::semaphore mSem;
384 ALuint mIdx{0};
385 std::array<WAVEHDR,4> mWaveBuffer{};
387 HWAVEIN mInHdl{nullptr};
389 RingBufferPtr mRing{nullptr};
391 WAVEFORMATEX mFormat{};
393 std::atomic<bool> mKillNow{true};
394 std::thread mThread;
396 DEF_NEWDEL(WinMMCapture)
399 WinMMCapture::~WinMMCapture()
401 // Close the Wave device
402 if(mInHdl)
403 waveInClose(mInHdl);
404 mInHdl = nullptr;
406 al_free(mWaveBuffer[0].lpData);
407 std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
410 void CALLBACK WinMMCapture::waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2)
411 { reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); }
413 /* WinMMCapture::waveInProc
415 * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
416 * completed and returns to the application (with more data).
418 void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR)
420 if(msg != WIM_DATA) return;
421 mReadable.fetch_add(1, std::memory_order_acq_rel);
422 mSem.post();
425 int WinMMCapture::captureProc()
427 althrd_setname(RECORD_THREAD_NAME);
429 lock();
430 while(!mKillNow.load(std::memory_order_acquire) &&
431 mDevice->Connected.load(std::memory_order_acquire))
433 ALuint todo{mReadable.load(std::memory_order_acquire)};
434 if(todo < 1)
436 unlock();
437 mSem.wait();
438 lock();
439 continue;
442 size_t widx{mIdx};
443 do {
444 WAVEHDR &waveHdr = mWaveBuffer[widx];
445 widx = (widx+1) % mWaveBuffer.size();
447 mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
448 mReadable.fetch_sub(1, std::memory_order_acq_rel);
449 waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
450 } while(--todo);
451 mIdx = static_cast<ALuint>(widx);
453 unlock();
455 return 0;
459 ALCenum WinMMCapture::open(const ALCchar *name)
461 if(CaptureDevices.empty())
462 ProbeCaptureDevices();
464 // Find the Device ID matching the deviceName if valid
465 auto iter = name ?
466 std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
467 CaptureDevices.cbegin();
468 if(iter == CaptureDevices.cend()) return ALC_INVALID_VALUE;
469 auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
471 switch(mDevice->FmtChans)
473 case DevFmtMono:
474 case DevFmtStereo:
475 break;
477 case DevFmtQuad:
478 case DevFmtX51:
479 case DevFmtX51Rear:
480 case DevFmtX61:
481 case DevFmtX71:
482 case DevFmtAmbi3D:
483 return ALC_INVALID_ENUM;
486 switch(mDevice->FmtType)
488 case DevFmtUByte:
489 case DevFmtShort:
490 case DevFmtInt:
491 case DevFmtFloat:
492 break;
494 case DevFmtByte:
495 case DevFmtUShort:
496 case DevFmtUInt:
497 return ALC_INVALID_ENUM;
500 mFormat = WAVEFORMATEX{};
501 mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ?
502 WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
503 mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
504 mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
505 mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
506 mFormat.nSamplesPerSec = mDevice->Frequency;
507 mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
508 mFormat.cbSize = 0;
510 MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat,
511 reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
512 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
513 if(res != MMSYSERR_NOERROR)
515 ERR("waveInOpen failed: %u\n", res);
516 return ALC_INVALID_VALUE;
519 // Ensure each buffer is 50ms each
520 DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
521 BufferSize -= (BufferSize % mFormat.nBlockAlign);
523 // Allocate circular memory buffer for the captured audio
524 // Make sure circular buffer is at least 100ms in size
525 ALuint CapturedDataSize{mDevice->BufferSize};
526 CapturedDataSize = static_cast<ALuint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
528 mRing = CreateRingBuffer(CapturedDataSize, mFormat.nBlockAlign, false);
529 if(!mRing) return ALC_INVALID_VALUE;
531 al_free(mWaveBuffer[0].lpData);
532 mWaveBuffer[0] = WAVEHDR{};
533 mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize*4));
534 mWaveBuffer[0].dwBufferLength = BufferSize;
535 for(size_t i{1};i < mWaveBuffer.size();++i)
537 mWaveBuffer[i] = WAVEHDR{};
538 mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
539 mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
542 mDevice->DeviceName = CaptureDevices[DeviceID];
543 return ALC_NO_ERROR;
546 bool WinMMCapture::start()
548 try {
549 for(size_t i{0};i < mWaveBuffer.size();++i)
551 waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
552 waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
555 mKillNow.store(false, std::memory_order_release);
556 mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
558 waveInStart(mInHdl);
559 return true;
561 catch(std::exception& e) {
562 ERR("Failed to start mixing thread: %s\n", e.what());
564 catch(...) {
566 return false;
569 void WinMMCapture::stop()
571 waveInStop(mInHdl);
573 mKillNow.store(true, std::memory_order_release);
574 if(mThread.joinable())
576 mSem.post();
577 mThread.join();
580 waveInReset(mInHdl);
581 for(size_t i{0};i < mWaveBuffer.size();++i)
582 waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
584 mReadable.store(0, std::memory_order_release);
585 mIdx = 0;
588 ALCenum WinMMCapture::captureSamples(al::byte *buffer, ALCuint samples)
590 mRing->read(buffer, samples);
591 return ALC_NO_ERROR;
594 ALCuint WinMMCapture::availableSamples()
595 { return static_cast<ALCuint>(mRing->readSpace()); }
597 } // namespace
600 bool WinMMBackendFactory::init()
601 { return true; }
603 bool WinMMBackendFactory::querySupport(BackendType type)
604 { return type == BackendType::Playback || type == BackendType::Capture; }
606 void WinMMBackendFactory::probe(DevProbe type, std::string *outnames)
608 auto add_device = [outnames](const std::string &dname) -> void
610 /* +1 to also append the null char (to ensure a null-separated list and
611 * double-null terminated list).
613 if(!dname.empty())
614 outnames->append(dname.c_str(), dname.length()+1);
616 switch(type)
618 case DevProbe::Playback:
619 ProbePlaybackDevices();
620 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
621 break;
623 case DevProbe::Capture:
624 ProbeCaptureDevices();
625 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
626 break;
630 BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type)
632 if(type == BackendType::Playback)
633 return BackendPtr{new WinMMPlayback{device}};
634 if(type == BackendType::Capture)
635 return BackendPtr{new WinMMCapture{device}};
636 return nullptr;
639 BackendFactory &WinMMBackendFactory::getFactory()
641 static WinMMBackendFactory factory{};
642 return factory;