Use an RAII wrapper to manage COM object references
[openal-soft.git] / alc / backends / winmm.cpp
blob274536b343f4121f0628c1728da5811faadc36cd
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>
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 "alcmain.h"
42 #include "alexcpt.h"
43 #include "alu.h"
44 #include "compat.h"
45 #include "logging.h"
46 #include "ringbuffer.h"
47 #include "strutils.h"
48 #include "threads.h"
50 #ifndef WAVE_FORMAT_IEEE_FLOAT
51 #define WAVE_FORMAT_IEEE_FLOAT 0x0003
52 #endif
54 namespace {
56 #define DEVNAME_HEAD "OpenAL Soft on "
59 al::vector<std::string> PlaybackDevices;
60 al::vector<std::string> CaptureDevices;
62 bool checkName(const al::vector<std::string> &list, const std::string &name)
63 { return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
65 void ProbePlaybackDevices(void)
67 PlaybackDevices.clear();
69 ALuint numdevs{waveOutGetNumDevs()};
70 PlaybackDevices.reserve(numdevs);
71 for(ALuint i{0};i < numdevs;i++)
73 std::string dname;
75 WAVEOUTCAPSW WaveCaps{};
76 if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
78 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
80 int count{1};
81 std::string newname{basename};
82 while(checkName(PlaybackDevices, newname))
84 newname = basename;
85 newname += " #";
86 newname += std::to_string(++count);
88 dname = std::move(newname);
90 TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
92 PlaybackDevices.emplace_back(std::move(dname));
96 void ProbeCaptureDevices(void)
98 CaptureDevices.clear();
100 ALuint numdevs{waveInGetNumDevs()};
101 CaptureDevices.reserve(numdevs);
102 for(ALuint i{0};i < numdevs;i++)
104 std::string dname;
106 WAVEINCAPSW WaveCaps{};
107 if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
109 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
111 int count{1};
112 std::string newname{basename};
113 while(checkName(CaptureDevices, newname))
115 newname = basename;
116 newname += " #";
117 newname += std::to_string(++count);
119 dname = std::move(newname);
121 TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
123 CaptureDevices.emplace_back(std::move(dname));
128 struct WinMMPlayback final : public BackendBase {
129 WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
130 ~WinMMPlayback() override;
132 void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
133 static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
134 { reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); }
136 int mixerProc();
138 void open(const ALCchar *name) override;
139 bool reset() override;
140 void start() override;
141 void stop() override;
143 std::atomic<ALuint> mWritable{0u};
144 al::semaphore mSem;
145 ALuint mIdx{0u};
146 std::array<WAVEHDR,4> mWaveBuffer{};
148 HWAVEOUT mOutHdl{nullptr};
150 WAVEFORMATEX mFormat{};
152 std::atomic<bool> mKillNow{true};
153 std::thread mThread;
155 DEF_NEWDEL(WinMMPlayback)
158 WinMMPlayback::~WinMMPlayback()
160 if(mOutHdl)
161 waveOutClose(mOutHdl);
162 mOutHdl = nullptr;
164 al_free(mWaveBuffer[0].lpData);
165 std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
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) noexcept
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 const size_t frame_step{mDevice->channelsFromFmt()};
187 while(!mKillNow.load(std::memory_order_acquire) &&
188 mDevice->Connected.load(std::memory_order_acquire))
190 ALsizei todo = mWritable.load(std::memory_order_acquire);
191 if(todo < 1)
193 mSem.wait();
194 continue;
197 size_t widx{mIdx};
198 do {
199 WAVEHDR &waveHdr = mWaveBuffer[widx];
200 widx = (widx+1) % mWaveBuffer.size();
202 mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, frame_step);
203 mWritable.fetch_sub(1, std::memory_order_acq_rel);
204 waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
205 } while(--todo);
206 mIdx = static_cast<ALuint>(widx);
209 return 0;
213 void WinMMPlayback::open(const ALCchar *name)
215 if(PlaybackDevices.empty())
216 ProbePlaybackDevices();
218 // Find the Device ID matching the deviceName if valid
219 auto iter = name ?
220 std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
221 PlaybackDevices.cbegin();
222 if(iter == PlaybackDevices.cend())
223 throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
224 auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
226 retry_open:
227 mFormat = WAVEFORMATEX{};
228 if(mDevice->FmtType == DevFmtFloat)
230 mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
231 mFormat.wBitsPerSample = 32;
233 else
235 mFormat.wFormatTag = WAVE_FORMAT_PCM;
236 if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte)
237 mFormat.wBitsPerSample = 8;
238 else
239 mFormat.wBitsPerSample = 16;
241 mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
242 mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
243 mFormat.nSamplesPerSec = mDevice->Frequency;
244 mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
245 mFormat.cbSize = 0;
247 MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat,
248 reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
249 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
250 if(res != MMSYSERR_NOERROR)
252 if(mDevice->FmtType == DevFmtFloat)
254 mDevice->FmtType = DevFmtShort;
255 goto retry_open;
257 throw al::backend_exception{ALC_INVALID_VALUE, "waveOutOpen failed: %u", res};
260 mDevice->DeviceName = PlaybackDevices[DeviceID];
263 bool WinMMPlayback::reset()
265 mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
266 mFormat.nSamplesPerSec / mDevice->Frequency);
267 mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u;
268 mDevice->UpdateSize = mDevice->BufferSize / 4;
269 mDevice->Frequency = mFormat.nSamplesPerSec;
271 if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
273 if(mFormat.wBitsPerSample == 32)
274 mDevice->FmtType = DevFmtFloat;
275 else
277 ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
278 return false;
281 else if(mFormat.wFormatTag == WAVE_FORMAT_PCM)
283 if(mFormat.wBitsPerSample == 16)
284 mDevice->FmtType = DevFmtShort;
285 else if(mFormat.wBitsPerSample == 8)
286 mDevice->FmtType = DevFmtUByte;
287 else
289 ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
290 return false;
293 else
295 ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
296 return false;
299 ALuint chanmask{};
300 if(mFormat.nChannels == 2)
302 mDevice->FmtChans = DevFmtStereo;
303 chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
305 else if(mFormat.nChannels == 1)
307 mDevice->FmtChans = DevFmtMono;
308 chanmask = SPEAKER_FRONT_CENTER;
310 else
312 ERR("Unhandled channel count: %d\n", mFormat.nChannels);
313 return false;
315 setChannelOrderFromWFXMask(chanmask);
317 ALuint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()};
319 al_free(mWaveBuffer[0].lpData);
320 mWaveBuffer[0] = WAVEHDR{};
321 mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
322 mWaveBuffer[0].dwBufferLength = BufferSize;
323 for(size_t i{1};i < mWaveBuffer.size();i++)
325 mWaveBuffer[i] = WAVEHDR{};
326 mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
327 mWaveBuffer[i].dwBufferLength = BufferSize;
329 mIdx = 0;
331 return true;
334 void WinMMPlayback::start()
336 try {
337 std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
338 [this](WAVEHDR &waveHdr) -> void
339 { waveOutPrepareHeader(mOutHdl, &waveHdr, static_cast<UINT>(sizeof(WAVEHDR))); }
341 mWritable.store(static_cast<ALuint>(mWaveBuffer.size()), std::memory_order_release);
343 mKillNow.store(false, std::memory_order_release);
344 mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
346 catch(std::exception& e) {
347 throw al::backend_exception{ALC_INVALID_DEVICE, "Failed to start mixing thread: %s",
348 e.what()};
352 void WinMMPlayback::stop()
354 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
355 return;
356 mThread.join();
358 while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
359 mSem.wait();
360 std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
361 [this](WAVEHDR &waveHdr) -> void
362 { waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); }
364 mWritable.store(0, std::memory_order_release);
368 struct WinMMCapture final : public BackendBase {
369 WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { }
370 ~WinMMCapture() override;
372 void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
373 static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
374 { reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); }
376 int captureProc();
378 void open(const ALCchar *name) override;
379 void start() override;
380 void stop() override;
381 ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
382 ALCuint availableSamples() override;
384 std::atomic<ALuint> mReadable{0u};
385 al::semaphore mSem;
386 ALuint mIdx{0};
387 std::array<WAVEHDR,4> mWaveBuffer{};
389 HWAVEIN mInHdl{nullptr};
391 RingBufferPtr mRing{nullptr};
393 WAVEFORMATEX mFormat{};
395 std::atomic<bool> mKillNow{true};
396 std::thread mThread;
398 DEF_NEWDEL(WinMMCapture)
401 WinMMCapture::~WinMMCapture()
403 // Close the Wave device
404 if(mInHdl)
405 waveInClose(mInHdl);
406 mInHdl = nullptr;
408 al_free(mWaveBuffer[0].lpData);
409 std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
412 /* WinMMCapture::waveInProc
414 * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
415 * completed and returns to the application (with more data).
417 void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
419 if(msg != WIM_DATA) return;
420 mReadable.fetch_add(1, std::memory_order_acq_rel);
421 mSem.post();
424 int WinMMCapture::captureProc()
426 althrd_setname(RECORD_THREAD_NAME);
428 while(!mKillNow.load(std::memory_order_acquire) &&
429 mDevice->Connected.load(std::memory_order_acquire))
431 ALuint todo{mReadable.load(std::memory_order_acquire)};
432 if(todo < 1)
434 mSem.wait();
435 continue;
438 size_t widx{mIdx};
439 do {
440 WAVEHDR &waveHdr = mWaveBuffer[widx];
441 widx = (widx+1) % mWaveBuffer.size();
443 mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
444 mReadable.fetch_sub(1, std::memory_order_acq_rel);
445 waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
446 } while(--todo);
447 mIdx = static_cast<ALuint>(widx);
450 return 0;
454 void WinMMCapture::open(const ALCchar *name)
456 if(CaptureDevices.empty())
457 ProbeCaptureDevices();
459 // Find the Device ID matching the deviceName if valid
460 auto iter = name ?
461 std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
462 CaptureDevices.cbegin();
463 if(iter == CaptureDevices.cend())
464 throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
465 auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
467 switch(mDevice->FmtChans)
469 case DevFmtMono:
470 case DevFmtStereo:
471 break;
473 case DevFmtQuad:
474 case DevFmtX51:
475 case DevFmtX51Rear:
476 case DevFmtX61:
477 case DevFmtX71:
478 case DevFmtAmbi3D:
479 throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported",
480 DevFmtChannelsString(mDevice->FmtChans)};
483 switch(mDevice->FmtType)
485 case DevFmtUByte:
486 case DevFmtShort:
487 case DevFmtInt:
488 case DevFmtFloat:
489 break;
491 case DevFmtByte:
492 case DevFmtUShort:
493 case DevFmtUInt:
494 throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported",
495 DevFmtTypeString(mDevice->FmtType)};
498 mFormat = WAVEFORMATEX{};
499 mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ?
500 WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
501 mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
502 mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
503 mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
504 mFormat.nSamplesPerSec = mDevice->Frequency;
505 mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
506 mFormat.cbSize = 0;
508 MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat,
509 reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
510 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
511 if(res != MMSYSERR_NOERROR)
512 throw al::backend_exception{ALC_INVALID_VALUE, "waveInOpen failed: %u", res};
514 // Ensure each buffer is 50ms each
515 DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
516 BufferSize -= (BufferSize % mFormat.nBlockAlign);
518 // Allocate circular memory buffer for the captured audio
519 // Make sure circular buffer is at least 100ms in size
520 ALuint CapturedDataSize{mDevice->BufferSize};
521 CapturedDataSize = static_cast<ALuint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
523 mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
525 al_free(mWaveBuffer[0].lpData);
526 mWaveBuffer[0] = WAVEHDR{};
527 mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
528 mWaveBuffer[0].dwBufferLength = BufferSize;
529 for(size_t i{1};i < mWaveBuffer.size();++i)
531 mWaveBuffer[i] = WAVEHDR{};
532 mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
533 mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
536 mDevice->DeviceName = CaptureDevices[DeviceID];
539 void WinMMCapture::start()
541 try {
542 for(size_t i{0};i < mWaveBuffer.size();++i)
544 waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
545 waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
548 mKillNow.store(false, std::memory_order_release);
549 mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
551 waveInStart(mInHdl);
553 catch(std::exception& e) {
554 throw al::backend_exception{ALC_INVALID_DEVICE, "Failed to start recording thread: %s",
555 e.what()};
559 void WinMMCapture::stop()
561 waveInStop(mInHdl);
563 mKillNow.store(true, std::memory_order_release);
564 if(mThread.joinable())
566 mSem.post();
567 mThread.join();
570 waveInReset(mInHdl);
571 for(size_t i{0};i < mWaveBuffer.size();++i)
572 waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
574 mReadable.store(0, std::memory_order_release);
575 mIdx = 0;
578 ALCenum WinMMCapture::captureSamples(al::byte *buffer, ALCuint samples)
580 mRing->read(buffer, samples);
581 return ALC_NO_ERROR;
584 ALCuint WinMMCapture::availableSamples()
585 { return static_cast<ALCuint>(mRing->readSpace()); }
587 } // namespace
590 bool WinMMBackendFactory::init()
591 { return true; }
593 bool WinMMBackendFactory::querySupport(BackendType type)
594 { return type == BackendType::Playback || type == BackendType::Capture; }
596 std::string WinMMBackendFactory::probe(BackendType type)
598 std::string outnames;
599 auto add_device = [&outnames](const std::string &dname) -> void
601 /* +1 to also append the null char (to ensure a null-separated list and
602 * double-null terminated list).
604 if(!dname.empty())
605 outnames.append(dname.c_str(), dname.length()+1);
607 switch(type)
609 case BackendType::Playback:
610 ProbePlaybackDevices();
611 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
612 break;
614 case BackendType::Capture:
615 ProbeCaptureDevices();
616 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
617 break;
619 return outnames;
622 BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type)
624 if(type == BackendType::Playback)
625 return BackendPtr{new WinMMPlayback{device}};
626 if(type == BackendType::Capture)
627 return BackendPtr{new WinMMCapture{device}};
628 return nullptr;
631 BackendFactory &WinMMBackendFactory::getFactory()
633 static WinMMBackendFactory factory{};
634 return factory;