Improve formatting for setting the UWP default device callback
[openal-soft.git] / alc / backends / winmm.cpp
blob8545472c8da67e80d768b6baa2d4aebb9e0b274a
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 "winmm.h"
25 #include <cstdlib>
26 #include <cstdio>
27 #include <memory.h>
29 #include <windows.h>
30 #include <mmsystem.h>
31 #include <mmreg.h>
33 #include <array>
34 #include <atomic>
35 #include <thread>
36 #include <vector>
37 #include <string>
38 #include <algorithm>
39 #include <functional>
41 #include "alsem.h"
42 #include "alstring.h"
43 #include "althrd_setname.h"
44 #include "core/device.h"
45 #include "core/helpers.h"
46 #include "core/logging.h"
47 #include "ringbuffer.h"
48 #include "strutils.h"
49 #include "vector.h"
51 #ifndef WAVE_FORMAT_IEEE_FLOAT
52 #define WAVE_FORMAT_IEEE_FLOAT 0x0003
53 #endif
55 namespace {
57 std::vector<std::string> PlaybackDevices;
58 std::vector<std::string> CaptureDevices;
60 bool checkName(const std::vector<std::string> &list, const std::string &name)
61 { return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
63 void ProbePlaybackDevices()
65 PlaybackDevices.clear();
67 UINT numdevs{waveOutGetNumDevs()};
68 PlaybackDevices.reserve(numdevs);
69 for(UINT i{0};i < numdevs;++i)
71 std::string dname;
73 WAVEOUTCAPSW WaveCaps{};
74 if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
76 const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname));
78 int count{1};
79 std::string newname{basename};
80 while(checkName(PlaybackDevices, newname))
82 newname = basename;
83 newname += " #";
84 newname += std::to_string(++count);
86 dname = std::move(newname);
88 TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
90 PlaybackDevices.emplace_back(std::move(dname));
94 void ProbeCaptureDevices()
96 CaptureDevices.clear();
98 UINT numdevs{waveInGetNumDevs()};
99 CaptureDevices.reserve(numdevs);
100 for(UINT i{0};i < numdevs;++i)
102 std::string dname;
104 WAVEINCAPSW WaveCaps{};
105 if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
107 const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname));
109 int count{1};
110 std::string newname{basename};
111 while(checkName(CaptureDevices, newname))
113 newname = basename;
114 newname += " #";
115 newname += std::to_string(++count);
117 dname = std::move(newname);
119 TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
121 CaptureDevices.emplace_back(std::move(dname));
126 struct WinMMPlayback final : public BackendBase {
127 WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
128 ~WinMMPlayback() override;
130 void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
131 static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
132 { reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); }
134 int mixerProc();
136 void open(std::string_view name) override;
137 bool reset() override;
138 void start() override;
139 void stop() override;
141 std::atomic<uint> mWritable{0u};
142 al::semaphore mSem;
143 uint mIdx{0u};
144 std::array<WAVEHDR,4> mWaveBuffer{};
145 al::vector<char,16> mBuffer;
147 HWAVEOUT mOutHdl{nullptr};
149 WAVEFORMATEX mFormat{};
151 std::atomic<bool> mKillNow{true};
152 std::thread mThread;
155 WinMMPlayback::~WinMMPlayback()
157 if(mOutHdl)
158 waveOutClose(mOutHdl);
159 mOutHdl = nullptr;
162 /* WinMMPlayback::waveOutProc
164 * Posts a message to 'WinMMPlayback::mixerProc' every time a WaveOut Buffer is
165 * completed and returns to the application (for more data)
167 void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
169 if(msg != WOM_DONE) return;
170 mWritable.fetch_add(1, std::memory_order_acq_rel);
171 mSem.post();
174 FORCE_ALIGN int WinMMPlayback::mixerProc()
176 SetRTPriority();
177 althrd_setname(GetMixerThreadName());
179 while(!mKillNow.load(std::memory_order_acquire)
180 && mDevice->Connected.load(std::memory_order_acquire))
182 uint todo{mWritable.load(std::memory_order_acquire)};
183 if(todo < 1)
185 mSem.wait();
186 continue;
189 size_t widx{mIdx};
190 do {
191 WAVEHDR &waveHdr = mWaveBuffer[widx];
192 if(++widx == mWaveBuffer.size()) widx = 0;
194 mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels);
195 mWritable.fetch_sub(1, std::memory_order_acq_rel);
196 waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
197 } while(--todo);
198 mIdx = static_cast<uint>(widx);
201 return 0;
205 void WinMMPlayback::open(std::string_view name)
207 if(PlaybackDevices.empty())
208 ProbePlaybackDevices();
210 // Find the Device ID matching the deviceName if valid
211 auto iter = !name.empty() ?
212 std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
213 PlaybackDevices.cbegin();
214 if(iter == PlaybackDevices.cend())
215 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
216 al::sizei(name), name.data()};
217 auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
219 DevFmtType fmttype{mDevice->FmtType};
220 WAVEFORMATEX format{};
221 do {
222 format = WAVEFORMATEX{};
223 if(fmttype == DevFmtFloat)
225 format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
226 format.wBitsPerSample = 32;
228 else
230 format.wFormatTag = WAVE_FORMAT_PCM;
231 if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
232 format.wBitsPerSample = 8;
233 else
234 format.wBitsPerSample = 16;
236 format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
237 format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
238 format.nSamplesPerSec = mDevice->Frequency;
239 format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
240 format.cbSize = 0;
242 MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &format,
243 reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
244 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
245 if(res == MMSYSERR_NOERROR) break;
247 if(fmttype != DevFmtFloat)
248 throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u",
249 res};
251 fmttype = DevFmtShort;
252 } while(true);
254 mFormat = format;
256 mDeviceName = PlaybackDevices[DeviceID];
259 bool WinMMPlayback::reset()
261 mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
262 mFormat.nSamplesPerSec / mDevice->Frequency);
263 mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u;
264 mDevice->UpdateSize = mDevice->BufferSize / 4;
265 mDevice->Frequency = mFormat.nSamplesPerSec;
267 if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
269 if(mFormat.wBitsPerSample == 32)
270 mDevice->FmtType = DevFmtFloat;
271 else
273 ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
274 return false;
277 else if(mFormat.wFormatTag == WAVE_FORMAT_PCM)
279 if(mFormat.wBitsPerSample == 16)
280 mDevice->FmtType = DevFmtShort;
281 else if(mFormat.wBitsPerSample == 8)
282 mDevice->FmtType = DevFmtUByte;
283 else
285 ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
286 return false;
289 else
291 ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
292 return false;
295 if(mFormat.nChannels >= 2)
296 mDevice->FmtChans = DevFmtStereo;
297 else if(mFormat.nChannels == 1)
298 mDevice->FmtChans = DevFmtMono;
299 else
301 ERR("Unhandled channel count: %d\n", mFormat.nChannels);
302 return false;
304 setDefaultWFXChannelOrder();
306 const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
308 decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
309 mWaveBuffer[0] = WAVEHDR{};
310 mWaveBuffer[0].lpData = mBuffer.data();
311 mWaveBuffer[0].dwBufferLength = BufferSize;
312 for(size_t i{1};i < mWaveBuffer.size();i++)
314 mWaveBuffer[i] = WAVEHDR{};
315 mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
316 mWaveBuffer[i].dwBufferLength = BufferSize;
318 mIdx = 0;
320 return true;
323 void WinMMPlayback::start()
325 try {
326 for(auto &waveHdr : mWaveBuffer)
327 waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR));
328 mWritable.store(static_cast<uint>(mWaveBuffer.size()), std::memory_order_release);
330 mKillNow.store(false, std::memory_order_release);
331 mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
333 catch(std::exception& e) {
334 throw al::backend_exception{al::backend_error::DeviceError,
335 "Failed to start mixing thread: %s", e.what()};
339 void WinMMPlayback::stop()
341 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
342 return;
343 mThread.join();
345 while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
346 mSem.wait();
347 for(auto &waveHdr : mWaveBuffer)
348 waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR));
349 mWritable.store(0, std::memory_order_release);
353 struct WinMMCapture final : public BackendBase {
354 WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { }
355 ~WinMMCapture() override;
357 void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
358 static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
359 { reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); }
361 int captureProc();
363 void open(std::string_view name) override;
364 void start() override;
365 void stop() override;
366 void captureSamples(std::byte *buffer, uint samples) override;
367 uint availableSamples() override;
369 std::atomic<uint> mReadable{0u};
370 al::semaphore mSem;
371 uint mIdx{0};
372 std::array<WAVEHDR,4> mWaveBuffer{};
373 al::vector<char,16> mBuffer;
375 HWAVEIN mInHdl{nullptr};
377 RingBufferPtr mRing{nullptr};
379 WAVEFORMATEX mFormat{};
381 std::atomic<bool> mKillNow{true};
382 std::thread mThread;
385 WinMMCapture::~WinMMCapture()
387 // Close the Wave device
388 if(mInHdl)
389 waveInClose(mInHdl);
390 mInHdl = nullptr;
393 /* WinMMCapture::waveInProc
395 * Posts a message to 'WinMMCapture::captureProc' every time a WaveIn Buffer is
396 * completed and returns to the application (with more data).
398 void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
400 if(msg != WIM_DATA) return;
401 mReadable.fetch_add(1, std::memory_order_acq_rel);
402 mSem.post();
405 int WinMMCapture::captureProc()
407 althrd_setname(GetRecordThreadName());
409 while(!mKillNow.load(std::memory_order_acquire) &&
410 mDevice->Connected.load(std::memory_order_acquire))
412 uint todo{mReadable.load(std::memory_order_acquire)};
413 if(todo < 1)
415 mSem.wait();
416 continue;
419 size_t widx{mIdx};
420 do {
421 WAVEHDR &waveHdr = mWaveBuffer[widx];
422 widx = (widx+1) % mWaveBuffer.size();
424 std::ignore = mRing->write(waveHdr.lpData,
425 waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
426 mReadable.fetch_sub(1, std::memory_order_acq_rel);
427 waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
428 } while(--todo);
429 mIdx = static_cast<uint>(widx);
432 return 0;
436 void WinMMCapture::open(std::string_view name)
438 if(CaptureDevices.empty())
439 ProbeCaptureDevices();
441 // Find the Device ID matching the deviceName if valid
442 auto iter = !name.empty() ?
443 std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
444 CaptureDevices.cbegin();
445 if(iter == CaptureDevices.cend())
446 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
447 al::sizei(name), name.data()};
448 auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
450 switch(mDevice->FmtChans)
452 case DevFmtMono:
453 case DevFmtStereo:
454 break;
456 case DevFmtQuad:
457 case DevFmtX51:
458 case DevFmtX61:
459 case DevFmtX71:
460 case DevFmtX714:
461 case DevFmtX7144:
462 case DevFmtX3D71:
463 case DevFmtAmbi3D:
464 throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
465 DevFmtChannelsString(mDevice->FmtChans)};
468 switch(mDevice->FmtType)
470 case DevFmtUByte:
471 case DevFmtShort:
472 case DevFmtInt:
473 case DevFmtFloat:
474 break;
476 case DevFmtByte:
477 case DevFmtUShort:
478 case DevFmtUInt:
479 throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
480 DevFmtTypeString(mDevice->FmtType)};
483 mFormat = WAVEFORMATEX{};
484 mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ?
485 WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
486 mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
487 mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
488 mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
489 mFormat.nSamplesPerSec = mDevice->Frequency;
490 mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
491 mFormat.cbSize = 0;
493 MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat,
494 reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
495 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
496 if(res != MMSYSERR_NOERROR)
497 throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res};
499 // Ensure each buffer is 50ms each
500 DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
501 BufferSize -= (BufferSize % mFormat.nBlockAlign);
503 // Allocate circular memory buffer for the captured audio
504 // Make sure circular buffer is at least 100ms in size
505 const auto CapturedDataSize = std::max<size_t>(mDevice->BufferSize,
506 BufferSize*mWaveBuffer.size());
508 mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
510 decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
511 mWaveBuffer[0] = WAVEHDR{};
512 mWaveBuffer[0].lpData = mBuffer.data();
513 mWaveBuffer[0].dwBufferLength = BufferSize;
514 for(size_t i{1};i < mWaveBuffer.size();++i)
516 mWaveBuffer[i] = WAVEHDR{};
517 mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
518 mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
521 mDeviceName = CaptureDevices[DeviceID];
524 void WinMMCapture::start()
526 try {
527 for(size_t i{0};i < mWaveBuffer.size();++i)
529 waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
530 waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
533 mKillNow.store(false, std::memory_order_release);
534 mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
536 waveInStart(mInHdl);
538 catch(std::exception& e) {
539 throw al::backend_exception{al::backend_error::DeviceError,
540 "Failed to start recording thread: %s", e.what()};
544 void WinMMCapture::stop()
546 waveInStop(mInHdl);
548 mKillNow.store(true, std::memory_order_release);
549 if(mThread.joinable())
551 mSem.post();
552 mThread.join();
555 waveInReset(mInHdl);
556 for(size_t i{0};i < mWaveBuffer.size();++i)
557 waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
559 mReadable.store(0, std::memory_order_release);
560 mIdx = 0;
563 void WinMMCapture::captureSamples(std::byte *buffer, uint samples)
564 { std::ignore = mRing->read(buffer, samples); }
566 uint WinMMCapture::availableSamples()
567 { return static_cast<uint>(mRing->readSpace()); }
569 } // namespace
572 bool WinMMBackendFactory::init()
573 { return true; }
575 bool WinMMBackendFactory::querySupport(BackendType type)
576 { return type == BackendType::Playback || type == BackendType::Capture; }
578 auto WinMMBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
580 std::vector<std::string> outnames;
581 auto add_device = [&outnames](const std::string &dname) -> void
582 { if(!dname.empty()) outnames.emplace_back(dname); };
584 switch(type)
586 case BackendType::Playback:
587 ProbePlaybackDevices();
588 outnames.reserve(PlaybackDevices.size());
589 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
590 break;
592 case BackendType::Capture:
593 ProbeCaptureDevices();
594 outnames.reserve(CaptureDevices.size());
595 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
596 break;
598 return outnames;
601 BackendPtr WinMMBackendFactory::createBackend(DeviceBase *device, BackendType type)
603 if(type == BackendType::Playback)
604 return BackendPtr{new WinMMPlayback{device}};
605 if(type == BackendType::Capture)
606 return BackendPtr{new WinMMCapture{device}};
607 return nullptr;
610 BackendFactory &WinMMBackendFactory::getFactory()
612 static WinMMBackendFactory factory{};
613 return factory;