Handle 3D7.1 as a separate channel configuration
[openal-soft.git] / alc / backends / winmm.cpp
blob14cc4f9ec5707eaf484a473c7fe844cdfd48f480
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 <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 "alnumeric.h"
42 #include "core/device.h"
43 #include "core/helpers.h"
44 #include "core/logging.h"
45 #include "ringbuffer.h"
46 #include "strutils.h"
47 #include "threads.h"
49 #ifndef WAVE_FORMAT_IEEE_FLOAT
50 #define WAVE_FORMAT_IEEE_FLOAT 0x0003
51 #endif
53 namespace {
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)
72 std::string dname;
74 WAVEOUTCAPSW WaveCaps{};
75 if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
77 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
79 int count{1};
80 std::string newname{basename};
81 while(checkName(PlaybackDevices, newname))
83 newname = basename;
84 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)
103 std::string dname;
105 WAVEINCAPSW WaveCaps{};
106 if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
108 const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
110 int count{1};
111 std::string newname{basename};
112 while(checkName(CaptureDevices, newname))
114 newname = basename;
115 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); }
135 int mixerProc();
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};
143 al::semaphore mSem;
144 uint mIdx{0u};
145 std::array<WAVEHDR,4> mWaveBuffer{};
147 HWAVEOUT mOutHdl{nullptr};
149 WAVEFORMATEX mFormat{};
151 std::atomic<bool> mKillNow{true};
152 std::thread mThread;
154 DEF_NEWDEL(WinMMPlayback)
157 WinMMPlayback::~WinMMPlayback()
159 if(mOutHdl)
160 waveOutClose(mOutHdl);
161 mOutHdl = nullptr;
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);
176 mSem.post();
179 FORCE_ALIGN int WinMMPlayback::mixerProc()
181 SetRTPriority();
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)};
188 if(todo < 1)
190 mSem.wait();
191 continue;
194 size_t widx{mIdx};
195 do {
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));
202 } while(--todo);
203 mIdx = static_cast<uint>(widx);
206 return 0;
210 void WinMMPlayback::open(const char *name)
212 if(PlaybackDevices.empty())
213 ProbePlaybackDevices();
215 // Find the Device ID matching the deviceName if valid
216 auto iter = name ?
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",
221 name};
222 auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
224 DevFmtType fmttype{mDevice->FmtType};
225 retry_open:
226 WAVEFORMATEX format{};
227 if(fmttype == DevFmtFloat)
229 format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
230 format.wBitsPerSample = 32;
232 else
234 format.wFormatTag = WAVE_FORMAT_PCM;
235 if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
236 format.wBitsPerSample = 8;
237 else
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;
244 format.cbSize = 0;
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;
255 goto retry_open;
257 throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
260 if(mOutHdl)
261 waveOutClose(mOutHdl);
262 mOutHdl = outHandle;
263 mFormat = format;
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;
280 else
282 ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
283 return false;
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;
292 else
294 ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
295 return false;
298 else
300 ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
301 return false;
304 if(mFormat.nChannels >= 2)
305 mDevice->FmtChans = DevFmtStereo;
306 else if(mFormat.nChannels == 1)
307 mDevice->FmtChans = DevFmtMono;
308 else
310 ERR("Unhandled channel count: %d\n", mFormat.nChannels);
311 return false;
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;
327 mIdx = 0;
329 return true;
332 void WinMMPlayback::start()
334 try {
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())
351 return;
352 mThread.join();
354 while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
355 mSem.wait();
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); }
370 int captureProc();
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};
379 al::semaphore mSem;
380 uint mIdx{0};
381 std::array<WAVEHDR,4> mWaveBuffer{};
383 HWAVEIN mInHdl{nullptr};
385 RingBufferPtr mRing{nullptr};
387 WAVEFORMATEX mFormat{};
389 std::atomic<bool> mKillNow{true};
390 std::thread mThread;
392 DEF_NEWDEL(WinMMCapture)
395 WinMMCapture::~WinMMCapture()
397 // Close the Wave device
398 if(mInHdl)
399 waveInClose(mInHdl);
400 mInHdl = nullptr;
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);
415 mSem.post();
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)};
426 if(todo < 1)
428 mSem.wait();
429 continue;
432 size_t widx{mIdx};
433 do {
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));
440 } while(--todo);
441 mIdx = static_cast<uint>(widx);
444 return 0;
448 void WinMMCapture::open(const char *name)
450 if(CaptureDevices.empty())
451 ProbeCaptureDevices();
453 // Find the Device ID matching the deviceName if valid
454 auto iter = name ?
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",
459 name};
460 auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
462 switch(mDevice->FmtChans)
464 case DevFmtMono:
465 case DevFmtStereo:
466 break;
468 case DevFmtQuad:
469 case DevFmtX51:
470 case DevFmtX61:
471 case DevFmtX71:
472 case DevFmtX3D71:
473 case DevFmtAmbi3D:
474 throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
475 DevFmtChannelsString(mDevice->FmtChans)};
478 switch(mDevice->FmtType)
480 case DevFmtUByte:
481 case DevFmtShort:
482 case DevFmtInt:
483 case DevFmtFloat:
484 break;
486 case DevFmtByte:
487 case DevFmtUShort:
488 case DevFmtUInt:
489 throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
490 DevFmtTypeString(mDevice->FmtType)};
493 mFormat = WAVEFORMATEX{};
494 mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ?
495 WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
496 mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
497 mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
498 mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
499 mFormat.nSamplesPerSec = mDevice->Frequency;
500 mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
501 mFormat.cbSize = 0;
503 MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat,
504 reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
505 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
506 if(res != MMSYSERR_NOERROR)
507 throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res};
509 // Ensure each buffer is 50ms each
510 DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
511 BufferSize -= (BufferSize % mFormat.nBlockAlign);
513 // Allocate circular memory buffer for the captured audio
514 // Make sure circular buffer is at least 100ms in size
515 uint CapturedDataSize{mDevice->BufferSize};
516 CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
518 mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
520 al_free(mWaveBuffer[0].lpData);
521 mWaveBuffer[0] = WAVEHDR{};
522 mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
523 mWaveBuffer[0].dwBufferLength = BufferSize;
524 for(size_t i{1};i < mWaveBuffer.size();++i)
526 mWaveBuffer[i] = WAVEHDR{};
527 mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
528 mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
531 mDevice->DeviceName = CaptureDevices[DeviceID];
534 void WinMMCapture::start()
536 try {
537 for(size_t i{0};i < mWaveBuffer.size();++i)
539 waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
540 waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
543 mKillNow.store(false, std::memory_order_release);
544 mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
546 waveInStart(mInHdl);
548 catch(std::exception& e) {
549 throw al::backend_exception{al::backend_error::DeviceError,
550 "Failed to start recording thread: %s", e.what()};
554 void WinMMCapture::stop()
556 waveInStop(mInHdl);
558 mKillNow.store(true, std::memory_order_release);
559 if(mThread.joinable())
561 mSem.post();
562 mThread.join();
565 waveInReset(mInHdl);
566 for(size_t i{0};i < mWaveBuffer.size();++i)
567 waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
569 mReadable.store(0, std::memory_order_release);
570 mIdx = 0;
573 void WinMMCapture::captureSamples(al::byte *buffer, uint samples)
574 { mRing->read(buffer, samples); }
576 uint WinMMCapture::availableSamples()
577 { return static_cast<uint>(mRing->readSpace()); }
579 } // namespace
582 bool WinMMBackendFactory::init()
583 { return true; }
585 bool WinMMBackendFactory::querySupport(BackendType type)
586 { return type == BackendType::Playback || type == BackendType::Capture; }
588 std::string WinMMBackendFactory::probe(BackendType type)
590 std::string outnames;
591 auto add_device = [&outnames](const std::string &dname) -> void
593 /* +1 to also append the null char (to ensure a null-separated list and
594 * double-null terminated list).
596 if(!dname.empty())
597 outnames.append(dname.c_str(), dname.length()+1);
599 switch(type)
601 case BackendType::Playback:
602 ProbePlaybackDevices();
603 std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
604 break;
606 case BackendType::Capture:
607 ProbeCaptureDevices();
608 std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
609 break;
611 return outnames;
614 BackendPtr WinMMBackendFactory::createBackend(DeviceBase *device, BackendType type)
616 if(type == BackendType::Playback)
617 return BackendPtr{new WinMMPlayback{device}};
618 if(type == BackendType::Capture)
619 return BackendPtr{new WinMMCapture{device}};
620 return nullptr;
623 BackendFactory &WinMMBackendFactory::getFactory()
625 static WinMMBackendFactory factory{};
626 return factory;