Support B-Format output with the wave file writer
[openal-soft.git] / Alc / backends / mmdevapi.c
blob95a0948cec2a4c1023e442d625aba241b8bd1241
1 /**
2 * OpenAL cross platform audio library
3 * Copyright (C) 2011 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 #define COBJMACROS
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <memory.h>
28 #include <mmdeviceapi.h>
29 #include <audioclient.h>
30 #include <cguid.h>
31 #include <devpropdef.h>
32 #include <mmreg.h>
33 #include <propsys.h>
34 #include <propkey.h>
35 #include <devpkey.h>
36 #ifndef _WAVEFORMATEXTENSIBLE_
37 #include <ks.h>
38 #include <ksmedia.h>
39 #endif
41 #include "alMain.h"
42 #include "alu.h"
43 #include "threads.h"
44 #include "compat.h"
45 #include "alstring.h"
47 #include "backends/base.h"
50 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
51 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
53 DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
55 #define MONO SPEAKER_FRONT_CENTER
56 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
57 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
58 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
59 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
60 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
61 #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
64 typedef struct {
65 al_string name;
66 WCHAR *devid;
67 } DevMap;
68 TYPEDEF_VECTOR(DevMap, vector_DevMap)
70 static void clear_devlist(vector_DevMap *list)
72 DevMap *iter, *end;
74 iter = VECTOR_ITER_BEGIN(*list);
75 end = VECTOR_ITER_END(*list);
76 for(;iter != end;iter++)
78 AL_STRING_DEINIT(iter->name);
79 free(iter->devid);
81 VECTOR_RESIZE(*list, 0);
84 static vector_DevMap PlaybackDevices;
85 static vector_DevMap CaptureDevices;
88 static HANDLE ThreadHdl;
89 static DWORD ThreadID;
91 typedef struct {
92 HANDLE FinishedEvt;
93 HRESULT result;
94 } ThreadRequest;
96 #define WM_USER_First (WM_USER+0)
97 #define WM_USER_OpenDevice (WM_USER+0)
98 #define WM_USER_ResetDevice (WM_USER+1)
99 #define WM_USER_StartDevice (WM_USER+2)
100 #define WM_USER_StopDevice (WM_USER+3)
101 #define WM_USER_CloseDevice (WM_USER+4)
102 #define WM_USER_Enumerate (WM_USER+5)
103 #define WM_USER_Last (WM_USER+5)
105 static inline void ReturnMsgResponse(ThreadRequest *req, HRESULT res)
107 req->result = res;
108 SetEvent(req->FinishedEvt);
111 static HRESULT WaitForResponse(ThreadRequest *req)
113 if(WaitForSingleObject(req->FinishedEvt, INFINITE) == WAIT_OBJECT_0)
114 return req->result;
115 ERR("Message response error: %lu\n", GetLastError());
116 return E_FAIL;
120 static void get_device_name(IMMDevice *device, al_string *name)
122 IPropertyStore *ps;
123 PROPVARIANT pvname;
124 HRESULT hr;
126 hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps);
127 if(FAILED(hr))
129 WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
130 return;
133 PropVariantInit(&pvname);
135 hr = IPropertyStore_GetValue(ps, (const PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &pvname);
136 if(FAILED(hr))
137 WARN("GetValue failed: 0x%08lx\n", hr);
138 else
139 al_string_copy_wcstr(name, pvname.pwszVal);
141 PropVariantClear(&pvname);
142 IPropertyStore_Release(ps);
145 static void add_device(IMMDevice *device, LPCWSTR devid, vector_DevMap *list)
147 DevMap entry;
149 AL_STRING_INIT(entry.name);
150 entry.devid = strdupW(devid);
151 get_device_name(device, &entry.name);
153 TRACE("Got device \"%s\", \"%ls\"\n", al_string_get_cstr(entry.name), entry.devid);
154 VECTOR_PUSH_BACK(*list, entry);
157 static LPWSTR get_device_id(IMMDevice *device)
159 LPWSTR devid;
160 HRESULT hr;
162 hr = IMMDevice_GetId(device, &devid);
163 if(FAILED(hr))
165 ERR("Failed to get device id: %lx\n", hr);
166 return NULL;
169 return devid;
172 static HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, vector_DevMap *list)
174 IMMDeviceCollection *coll;
175 IMMDevice *defdev = NULL;
176 LPWSTR defdevid = NULL;
177 HRESULT hr;
178 UINT count;
179 UINT i;
181 hr = IMMDeviceEnumerator_EnumAudioEndpoints(devenum, flowdir, DEVICE_STATE_ACTIVE, &coll);
182 if(FAILED(hr))
184 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
185 return hr;
188 count = 0;
189 hr = IMMDeviceCollection_GetCount(coll, &count);
190 if(SUCCEEDED(hr) && count > 0)
192 clear_devlist(list);
193 if(!VECTOR_RESERVE(*list, count))
195 IMMDeviceCollection_Release(coll);
196 return E_OUTOFMEMORY;
199 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devenum, flowdir,
200 eMultimedia, &defdev);
202 if(SUCCEEDED(hr) && defdev != NULL)
204 defdevid = get_device_id(defdev);
205 if(defdevid)
206 add_device(defdev, defdevid, list);
209 for(i = 0;i < count;++i)
211 IMMDevice *device;
212 LPWSTR devid;
214 hr = IMMDeviceCollection_Item(coll, i, &device);
215 if(FAILED(hr)) continue;
217 devid = get_device_id(device);
218 if(devid)
220 if(wcscmp(devid, defdevid) != 0)
221 add_device(device, devid, list);
222 CoTaskMemFree(devid);
224 IMMDevice_Release(device);
227 if(defdev) IMMDevice_Release(defdev);
228 if(defdevid) CoTaskMemFree(defdevid);
229 IMMDeviceCollection_Release(coll);
231 return S_OK;
235 /* Proxy interface used by the message handler. */
236 struct ALCmmdevProxyVtable;
238 typedef struct ALCmmdevProxy {
239 const struct ALCmmdevProxyVtable *vtbl;
240 } ALCmmdevProxy;
242 struct ALCmmdevProxyVtable {
243 HRESULT (*const openProxy)(ALCmmdevProxy*);
244 void (*const closeProxy)(ALCmmdevProxy*);
246 HRESULT (*const resetProxy)(ALCmmdevProxy*);
247 HRESULT (*const startProxy)(ALCmmdevProxy*);
248 void (*const stopProxy)(ALCmmdevProxy*);
251 #define DEFINE_ALCMMDEVPROXY_VTABLE(T) \
252 DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, openProxy) \
253 DECLARE_THUNK(T, ALCmmdevProxy, void, closeProxy) \
254 DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, resetProxy) \
255 DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, startProxy) \
256 DECLARE_THUNK(T, ALCmmdevProxy, void, stopProxy) \
258 static const struct ALCmmdevProxyVtable T##_ALCmmdevProxy_vtable = { \
259 T##_ALCmmdevProxy_openProxy, \
260 T##_ALCmmdevProxy_closeProxy, \
261 T##_ALCmmdevProxy_resetProxy, \
262 T##_ALCmmdevProxy_startProxy, \
263 T##_ALCmmdevProxy_stopProxy, \
266 static void ALCmmdevProxy_Construct(ALCmmdevProxy* UNUSED(self)) { }
267 static void ALCmmdevProxy_Destruct(ALCmmdevProxy* UNUSED(self)) { }
269 static DWORD CALLBACK ALCmmdevProxy_messageHandler(void *ptr)
271 ThreadRequest *req = ptr;
272 IMMDeviceEnumerator *Enumerator;
273 ALuint deviceCount = 0;
274 ALCmmdevProxy *proxy;
275 HRESULT hr, cohr;
276 MSG msg;
278 TRACE("Starting message thread\n");
280 cohr = CoInitialize(NULL);
281 if(FAILED(cohr))
283 WARN("Failed to initialize COM: 0x%08lx\n", cohr);
284 ReturnMsgResponse(req, cohr);
285 return 0;
288 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr);
289 if(FAILED(hr))
291 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
292 CoUninitialize();
293 ReturnMsgResponse(req, hr);
294 return 0;
296 Enumerator = ptr;
297 IMMDeviceEnumerator_Release(Enumerator);
298 Enumerator = NULL;
300 CoUninitialize();
302 /* HACK: Force Windows to create a message queue for this thread before
303 * returning success, otherwise PostThreadMessage may fail if it gets
304 * called before GetMessage.
306 PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
308 TRACE("Message thread initialization complete\n");
309 ReturnMsgResponse(req, S_OK);
311 TRACE("Starting message loop\n");
312 while(GetMessage(&msg, NULL, WM_USER_First, WM_USER_Last))
314 TRACE("Got message %u\n", msg.message);
315 switch(msg.message)
317 case WM_USER_OpenDevice:
318 req = (ThreadRequest*)msg.wParam;
319 proxy = (ALCmmdevProxy*)msg.lParam;
321 hr = cohr = S_OK;
322 if(++deviceCount == 1)
323 hr = cohr = CoInitialize(NULL);
324 if(SUCCEEDED(hr))
325 hr = V0(proxy,openProxy)();
326 if(FAILED(hr))
328 if(--deviceCount == 0 && SUCCEEDED(cohr))
329 CoUninitialize();
332 ReturnMsgResponse(req, hr);
333 continue;
335 case WM_USER_ResetDevice:
336 req = (ThreadRequest*)msg.wParam;
337 proxy = (ALCmmdevProxy*)msg.lParam;
339 hr = V0(proxy,resetProxy)();
340 ReturnMsgResponse(req, hr);
341 continue;
343 case WM_USER_StartDevice:
344 req = (ThreadRequest*)msg.wParam;
345 proxy = (ALCmmdevProxy*)msg.lParam;
347 hr = V0(proxy,startProxy)();
348 ReturnMsgResponse(req, hr);
349 continue;
351 case WM_USER_StopDevice:
352 req = (ThreadRequest*)msg.wParam;
353 proxy = (ALCmmdevProxy*)msg.lParam;
355 V0(proxy,stopProxy)();
356 ReturnMsgResponse(req, S_OK);
357 continue;
359 case WM_USER_CloseDevice:
360 req = (ThreadRequest*)msg.wParam;
361 proxy = (ALCmmdevProxy*)msg.lParam;
363 V0(proxy,closeProxy)();
364 if(--deviceCount == 0)
365 CoUninitialize();
367 ReturnMsgResponse(req, S_OK);
368 continue;
370 case WM_USER_Enumerate:
371 req = (ThreadRequest*)msg.wParam;
373 hr = cohr = S_OK;
374 if(++deviceCount == 1)
375 hr = cohr = CoInitialize(NULL);
376 if(SUCCEEDED(hr))
377 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr);
378 if(SUCCEEDED(hr))
380 Enumerator = ptr;
382 if(msg.lParam == ALL_DEVICE_PROBE)
383 hr = probe_devices(Enumerator, eRender, &PlaybackDevices);
384 else if(msg.lParam == CAPTURE_DEVICE_PROBE)
385 hr = probe_devices(Enumerator, eCapture, &CaptureDevices);
387 IMMDeviceEnumerator_Release(Enumerator);
388 Enumerator = NULL;
391 if(--deviceCount == 0 && SUCCEEDED(cohr))
392 CoUninitialize();
394 ReturnMsgResponse(req, hr);
395 continue;
397 default:
398 ERR("Unexpected message: %u\n", msg.message);
399 continue;
402 TRACE("Message loop finished\n");
404 return 0;
408 typedef struct ALCmmdevPlayback {
409 DERIVE_FROM_TYPE(ALCbackend);
410 DERIVE_FROM_TYPE(ALCmmdevProxy);
412 WCHAR *devid;
414 IMMDevice *mmdev;
415 IAudioClient *client;
416 IAudioRenderClient *render;
417 HANDLE NotifyEvent;
419 HANDLE MsgEvent;
421 volatile UINT32 Padding;
423 volatile int killNow;
424 althrd_t thread;
425 } ALCmmdevPlayback;
427 static int ALCmmdevPlayback_mixerProc(void *arg);
429 static void ALCmmdevPlayback_Construct(ALCmmdevPlayback *self, ALCdevice *device);
430 static void ALCmmdevPlayback_Destruct(ALCmmdevPlayback *self);
431 static ALCenum ALCmmdevPlayback_open(ALCmmdevPlayback *self, const ALCchar *name);
432 static HRESULT ALCmmdevPlayback_openProxy(ALCmmdevPlayback *self);
433 static void ALCmmdevPlayback_close(ALCmmdevPlayback *self);
434 static void ALCmmdevPlayback_closeProxy(ALCmmdevPlayback *self);
435 static ALCboolean ALCmmdevPlayback_reset(ALCmmdevPlayback *self);
436 static HRESULT ALCmmdevPlayback_resetProxy(ALCmmdevPlayback *self);
437 static ALCboolean ALCmmdevPlayback_start(ALCmmdevPlayback *self);
438 static HRESULT ALCmmdevPlayback_startProxy(ALCmmdevPlayback *self);
439 static void ALCmmdevPlayback_stop(ALCmmdevPlayback *self);
440 static void ALCmmdevPlayback_stopProxy(ALCmmdevPlayback *self);
441 static DECLARE_FORWARD2(ALCmmdevPlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint)
442 static DECLARE_FORWARD(ALCmmdevPlayback, ALCbackend, ALCuint, availableSamples)
443 static ALint64 ALCmmdevPlayback_getLatency(ALCmmdevPlayback *self);
444 static DECLARE_FORWARD(ALCmmdevPlayback, ALCbackend, void, lock)
445 static DECLARE_FORWARD(ALCmmdevPlayback, ALCbackend, void, unlock)
446 DECLARE_DEFAULT_ALLOCATORS(ALCmmdevPlayback)
448 DEFINE_ALCMMDEVPROXY_VTABLE(ALCmmdevPlayback);
449 DEFINE_ALCBACKEND_VTABLE(ALCmmdevPlayback);
452 static void ALCmmdevPlayback_Construct(ALCmmdevPlayback *self, ALCdevice *device)
454 SET_VTABLE2(ALCmmdevPlayback, ALCbackend, self);
455 SET_VTABLE2(ALCmmdevPlayback, ALCmmdevProxy, self);
456 ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
457 ALCmmdevProxy_Construct(STATIC_CAST(ALCmmdevProxy, self));
459 self->devid = NULL;
461 self->mmdev = NULL;
462 self->client = NULL;
463 self->render = NULL;
464 self->NotifyEvent = NULL;
466 self->MsgEvent = NULL;
468 self->Padding = 0;
470 self->killNow = 0;
473 static void ALCmmdevPlayback_Destruct(ALCmmdevPlayback *self)
475 if(self->NotifyEvent != NULL)
476 CloseHandle(self->NotifyEvent);
477 self->NotifyEvent = NULL;
478 if(self->MsgEvent != NULL)
479 CloseHandle(self->MsgEvent);
480 self->MsgEvent = NULL;
482 free(self->devid);
483 self->devid = NULL;
485 ALCmmdevProxy_Destruct(STATIC_CAST(ALCmmdevProxy, self));
486 ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
490 FORCE_ALIGN static int ALCmmdevPlayback_mixerProc(void *arg)
492 ALCmmdevPlayback *self = arg;
493 ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
494 UINT32 buffer_len, written;
495 ALuint update_size, len;
496 BYTE *buffer;
497 HRESULT hr;
499 hr = CoInitialize(NULL);
500 if(FAILED(hr))
502 ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr);
503 ALCdevice_Lock(device);
504 aluHandleDisconnect(device);
505 ALCdevice_Unlock(device);
506 return 1;
509 SetRTPriority();
510 althrd_setname(althrd_current(), MIXER_THREAD_NAME);
512 update_size = device->UpdateSize;
513 buffer_len = update_size * device->NumUpdates;
514 while(!self->killNow)
516 hr = IAudioClient_GetCurrentPadding(self->client, &written);
517 if(FAILED(hr))
519 ERR("Failed to get padding: 0x%08lx\n", hr);
520 ALCdevice_Lock(device);
521 aluHandleDisconnect(device);
522 ALCdevice_Unlock(device);
523 break;
525 self->Padding = written;
527 len = buffer_len - written;
528 if(len < update_size)
530 DWORD res;
531 res = WaitForSingleObjectEx(self->NotifyEvent, 2000, FALSE);
532 if(res != WAIT_OBJECT_0)
533 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
534 continue;
536 len -= len%update_size;
538 hr = IAudioRenderClient_GetBuffer(self->render, len, &buffer);
539 if(SUCCEEDED(hr))
541 ALCdevice_Lock(device);
542 aluMixData(device, buffer, len);
543 self->Padding = written + len;
544 ALCdevice_Unlock(device);
545 hr = IAudioRenderClient_ReleaseBuffer(self->render, len, 0);
547 if(FAILED(hr))
549 ERR("Failed to buffer data: 0x%08lx\n", hr);
550 ALCdevice_Lock(device);
551 aluHandleDisconnect(device);
552 ALCdevice_Unlock(device);
553 break;
556 self->Padding = 0;
558 CoUninitialize();
559 return 0;
563 static ALCboolean MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in)
565 memset(out, 0, sizeof(*out));
566 if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
567 *out = *(const WAVEFORMATEXTENSIBLE*)in;
568 else if(in->wFormatTag == WAVE_FORMAT_PCM)
570 out->Format = *in;
571 out->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
572 out->Format.cbSize = sizeof(*out) - sizeof(*in);
573 if(out->Format.nChannels == 1)
574 out->dwChannelMask = MONO;
575 else if(out->Format.nChannels == 2)
576 out->dwChannelMask = STEREO;
577 else
578 ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels);
579 out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
581 else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
583 out->Format = *in;
584 out->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
585 out->Format.cbSize = sizeof(*out) - sizeof(*in);
586 if(out->Format.nChannels == 1)
587 out->dwChannelMask = MONO;
588 else if(out->Format.nChannels == 2)
589 out->dwChannelMask = STEREO;
590 else
591 ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels);
592 out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
594 else
596 ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag);
597 return ALC_FALSE;
599 return ALC_TRUE;
603 static ALCenum ALCmmdevPlayback_open(ALCmmdevPlayback *self, const ALCchar *deviceName)
605 HRESULT hr = S_OK;
607 self->NotifyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
608 self->MsgEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
609 if(self->NotifyEvent == NULL || self->MsgEvent == NULL)
611 ERR("Failed to create message events: %lu\n", GetLastError());
612 hr = E_FAIL;
615 if(SUCCEEDED(hr))
617 if(deviceName)
619 const DevMap *iter, *end;
621 if(VECTOR_SIZE(PlaybackDevices) == 0)
623 ThreadRequest req = { self->MsgEvent, 0 };
624 if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, ALL_DEVICE_PROBE))
625 (void)WaitForResponse(&req);
628 hr = E_FAIL;
629 iter = VECTOR_ITER_BEGIN(PlaybackDevices);
630 end = VECTOR_ITER_END(PlaybackDevices);
631 for(;iter != end;iter++)
633 if(al_string_cmp_cstr(iter->name, deviceName) == 0)
635 self->devid = strdupW(iter->devid);
636 hr = S_OK;
637 break;
640 if(FAILED(hr))
641 WARN("Failed to find device name matching \"%s\"\n", deviceName);
645 if(SUCCEEDED(hr))
647 ThreadRequest req = { self->MsgEvent, 0 };
649 hr = E_FAIL;
650 if(PostThreadMessage(ThreadID, WM_USER_OpenDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self)))
651 hr = WaitForResponse(&req);
652 else
653 ERR("Failed to post thread message: %lu\n", GetLastError());
656 if(FAILED(hr))
658 if(self->NotifyEvent != NULL)
659 CloseHandle(self->NotifyEvent);
660 self->NotifyEvent = NULL;
661 if(self->MsgEvent != NULL)
662 CloseHandle(self->MsgEvent);
663 self->MsgEvent = NULL;
665 free(self->devid);
666 self->devid = NULL;
668 ERR("Device init failed: 0x%08lx\n", hr);
669 return ALC_INVALID_VALUE;
672 return ALC_NO_ERROR;
675 static HRESULT ALCmmdevPlayback_openProxy(ALCmmdevPlayback *self)
677 ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
678 void *ptr;
679 HRESULT hr;
681 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr);
682 if(SUCCEEDED(hr))
684 IMMDeviceEnumerator *Enumerator = ptr;
685 if(!self->devid)
686 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator, eRender, eMultimedia, &self->mmdev);
687 else
688 hr = IMMDeviceEnumerator_GetDevice(Enumerator, self->devid, &self->mmdev);
689 IMMDeviceEnumerator_Release(Enumerator);
690 Enumerator = NULL;
692 if(SUCCEEDED(hr))
693 hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr);
694 if(SUCCEEDED(hr))
696 self->client = ptr;
697 get_device_name(self->mmdev, &device->DeviceName);
700 if(FAILED(hr))
702 if(self->mmdev)
703 IMMDevice_Release(self->mmdev);
704 self->mmdev = NULL;
707 return hr;
711 static void ALCmmdevPlayback_close(ALCmmdevPlayback *self)
713 ThreadRequest req = { self->MsgEvent, 0 };
715 if(PostThreadMessage(ThreadID, WM_USER_CloseDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self)))
716 (void)WaitForResponse(&req);
718 CloseHandle(self->MsgEvent);
719 self->MsgEvent = NULL;
721 CloseHandle(self->NotifyEvent);
722 self->NotifyEvent = NULL;
724 free(self->devid);
725 self->devid = NULL;
728 static void ALCmmdevPlayback_closeProxy(ALCmmdevPlayback *self)
730 if(self->client)
731 IAudioClient_Release(self->client);
732 self->client = NULL;
734 if(self->mmdev)
735 IMMDevice_Release(self->mmdev);
736 self->mmdev = NULL;
740 static ALCboolean ALCmmdevPlayback_reset(ALCmmdevPlayback *self)
742 ThreadRequest req = { self->MsgEvent, 0 };
743 HRESULT hr = E_FAIL;
745 if(PostThreadMessage(ThreadID, WM_USER_ResetDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self)))
746 hr = WaitForResponse(&req);
748 return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE;
751 static HRESULT ALCmmdevPlayback_resetProxy(ALCmmdevPlayback *self)
753 ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
754 WAVEFORMATEXTENSIBLE OutputType;
755 WAVEFORMATEX *wfx = NULL;
756 REFERENCE_TIME min_per, buf_time;
757 UINT32 buffer_len, min_len;
758 void *ptr = NULL;
759 HRESULT hr;
761 if(self->client)
762 IAudioClient_Release(self->client);
763 self->client = NULL;
765 hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr);
766 if(FAILED(hr))
768 ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
769 return hr;
771 self->client = ptr;
773 hr = IAudioClient_GetMixFormat(self->client, &wfx);
774 if(FAILED(hr))
776 ERR("Failed to get mix format: 0x%08lx\n", hr);
777 return hr;
780 if(!MakeExtensible(&OutputType, wfx))
782 CoTaskMemFree(wfx);
783 return E_FAIL;
785 CoTaskMemFree(wfx);
786 wfx = NULL;
788 buf_time = ((REFERENCE_TIME)device->UpdateSize*device->NumUpdates*10000000 +
789 device->Frequency-1) / device->Frequency;
791 if(!(device->Flags&DEVICE_FREQUENCY_REQUEST))
792 device->Frequency = OutputType.Format.nSamplesPerSec;
793 if(!(device->Flags&DEVICE_CHANNELS_REQUEST))
795 if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO)
796 device->FmtChans = DevFmtMono;
797 else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO)
798 device->FmtChans = DevFmtStereo;
799 else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD)
800 device->FmtChans = DevFmtQuad;
801 else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1)
802 device->FmtChans = DevFmtX51;
803 else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR)
804 device->FmtChans = DevFmtX51Rear;
805 else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1)
806 device->FmtChans = DevFmtX61;
807 else if(OutputType.Format.nChannels == 8 && OutputType.dwChannelMask == X7DOT1)
808 device->FmtChans = DevFmtX71;
809 else
810 ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask);
813 switch(device->FmtChans)
815 case DevFmtMono:
816 OutputType.Format.nChannels = 1;
817 OutputType.dwChannelMask = MONO;
818 break;
819 case DevFmtBFormat3D:
820 device->FmtChans = DevFmtStereo;
821 /*fall-through*/
822 case DevFmtStereo:
823 OutputType.Format.nChannels = 2;
824 OutputType.dwChannelMask = STEREO;
825 break;
826 case DevFmtQuad:
827 OutputType.Format.nChannels = 4;
828 OutputType.dwChannelMask = QUAD;
829 break;
830 case DevFmtX51:
831 OutputType.Format.nChannels = 6;
832 OutputType.dwChannelMask = X5DOT1;
833 break;
834 case DevFmtX51Rear:
835 OutputType.Format.nChannels = 6;
836 OutputType.dwChannelMask = X5DOT1REAR;
837 break;
838 case DevFmtX61:
839 OutputType.Format.nChannels = 7;
840 OutputType.dwChannelMask = X6DOT1;
841 break;
842 case DevFmtX71:
843 OutputType.Format.nChannels = 8;
844 OutputType.dwChannelMask = X7DOT1;
845 break;
847 switch(device->FmtType)
849 case DevFmtByte:
850 device->FmtType = DevFmtUByte;
851 /* fall-through */
852 case DevFmtUByte:
853 OutputType.Format.wBitsPerSample = 8;
854 OutputType.Samples.wValidBitsPerSample = 8;
855 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
856 break;
857 case DevFmtUShort:
858 device->FmtType = DevFmtShort;
859 /* fall-through */
860 case DevFmtShort:
861 OutputType.Format.wBitsPerSample = 16;
862 OutputType.Samples.wValidBitsPerSample = 16;
863 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
864 break;
865 case DevFmtUInt:
866 device->FmtType = DevFmtInt;
867 /* fall-through */
868 case DevFmtInt:
869 OutputType.Format.wBitsPerSample = 32;
870 OutputType.Samples.wValidBitsPerSample = 32;
871 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
872 break;
873 case DevFmtFloat:
874 OutputType.Format.wBitsPerSample = 32;
875 OutputType.Samples.wValidBitsPerSample = 32;
876 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
877 break;
879 OutputType.Format.nSamplesPerSec = device->Frequency;
881 OutputType.Format.nBlockAlign = OutputType.Format.nChannels *
882 OutputType.Format.wBitsPerSample / 8;
883 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
884 OutputType.Format.nBlockAlign;
886 hr = IAudioClient_IsFormatSupported(self->client, AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
887 if(FAILED(hr))
889 ERR("Failed to check format support: 0x%08lx\n", hr);
890 hr = IAudioClient_GetMixFormat(self->client, &wfx);
892 if(FAILED(hr))
894 ERR("Failed to find a supported format: 0x%08lx\n", hr);
895 return hr;
898 if(wfx != NULL)
900 if(!MakeExtensible(&OutputType, wfx))
902 CoTaskMemFree(wfx);
903 return E_FAIL;
905 CoTaskMemFree(wfx);
906 wfx = NULL;
908 device->Frequency = OutputType.Format.nSamplesPerSec;
909 if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO)
910 device->FmtChans = DevFmtMono;
911 else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO)
912 device->FmtChans = DevFmtStereo;
913 else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD)
914 device->FmtChans = DevFmtQuad;
915 else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1)
916 device->FmtChans = DevFmtX51;
917 else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR)
918 device->FmtChans = DevFmtX51Rear;
919 else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1)
920 device->FmtChans = DevFmtX61;
921 else if(OutputType.Format.nChannels == 8 && OutputType.dwChannelMask == X7DOT1)
922 device->FmtChans = DevFmtX71;
923 else
925 ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask);
926 device->FmtChans = DevFmtStereo;
927 OutputType.Format.nChannels = 2;
928 OutputType.dwChannelMask = STEREO;
931 if(IsEqualGUID(&OutputType.SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))
933 if(OutputType.Format.wBitsPerSample == 8)
934 device->FmtType = DevFmtUByte;
935 else if(OutputType.Format.wBitsPerSample == 16)
936 device->FmtType = DevFmtShort;
937 else if(OutputType.Format.wBitsPerSample == 32)
938 device->FmtType = DevFmtInt;
939 else
941 device->FmtType = DevFmtShort;
942 OutputType.Format.wBitsPerSample = 16;
945 else if(IsEqualGUID(&OutputType.SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
947 device->FmtType = DevFmtFloat;
948 OutputType.Format.wBitsPerSample = 32;
950 else
952 ERR("Unhandled format sub-type\n");
953 device->FmtType = DevFmtShort;
954 OutputType.Format.wBitsPerSample = 16;
955 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
957 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
960 SetDefaultWFXChannelOrder(device);
962 hr = IAudioClient_Initialize(self->client, AUDCLNT_SHAREMODE_SHARED,
963 AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
964 buf_time, 0, &OutputType.Format, NULL);
965 if(FAILED(hr))
967 ERR("Failed to initialize audio client: 0x%08lx\n", hr);
968 return hr;
971 hr = IAudioClient_GetDevicePeriod(self->client, &min_per, NULL);
972 if(SUCCEEDED(hr))
974 min_len = (UINT32)((min_per*device->Frequency + 10000000-1) / 10000000);
975 /* Find the nearest multiple of the period size to the update size */
976 if(min_len < device->UpdateSize)
977 min_len *= (device->UpdateSize + min_len/2)/min_len;
978 hr = IAudioClient_GetBufferSize(self->client, &buffer_len);
980 if(FAILED(hr))
982 ERR("Failed to get audio buffer info: 0x%08lx\n", hr);
983 return hr;
986 device->UpdateSize = min_len;
987 device->NumUpdates = buffer_len / device->UpdateSize;
988 if(device->NumUpdates <= 1)
990 ERR("Audio client returned buffer_len < period*2; expect break up\n");
991 device->NumUpdates = 2;
992 device->UpdateSize = buffer_len / device->NumUpdates;
995 hr = IAudioClient_SetEventHandle(self->client, self->NotifyEvent);
996 if(FAILED(hr))
998 ERR("Failed to set event handle: 0x%08lx\n", hr);
999 return hr;
1002 return hr;
1006 static ALCboolean ALCmmdevPlayback_start(ALCmmdevPlayback *self)
1008 ThreadRequest req = { self->MsgEvent, 0 };
1009 HRESULT hr = E_FAIL;
1011 if(PostThreadMessage(ThreadID, WM_USER_StartDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self)))
1012 hr = WaitForResponse(&req);
1014 return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE;
1017 static HRESULT ALCmmdevPlayback_startProxy(ALCmmdevPlayback *self)
1019 HRESULT hr;
1020 void *ptr;
1022 ResetEvent(self->NotifyEvent);
1023 hr = IAudioClient_Start(self->client);
1024 if(FAILED(hr))
1025 ERR("Failed to start audio client: 0x%08lx\n", hr);
1027 if(SUCCEEDED(hr))
1028 hr = IAudioClient_GetService(self->client, &IID_IAudioRenderClient, &ptr);
1029 if(SUCCEEDED(hr))
1031 self->render = ptr;
1032 self->killNow = 0;
1033 if(althrd_create(&self->thread, ALCmmdevPlayback_mixerProc, self) != althrd_success)
1035 if(self->render)
1036 IAudioRenderClient_Release(self->render);
1037 self->render = NULL;
1038 IAudioClient_Stop(self->client);
1039 ERR("Failed to start thread\n");
1040 hr = E_FAIL;
1044 return hr;
1048 static void ALCmmdevPlayback_stop(ALCmmdevPlayback *self)
1050 ThreadRequest req = { self->MsgEvent, 0 };
1051 if(PostThreadMessage(ThreadID, WM_USER_StopDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self)))
1052 (void)WaitForResponse(&req);
1055 static void ALCmmdevPlayback_stopProxy(ALCmmdevPlayback *self)
1057 int res;
1059 if(!self->render)
1060 return;
1062 self->killNow = 1;
1063 althrd_join(self->thread, &res);
1065 IAudioRenderClient_Release(self->render);
1066 self->render = NULL;
1067 IAudioClient_Stop(self->client);
1071 static ALint64 ALCmmdevPlayback_getLatency(ALCmmdevPlayback *self)
1073 ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
1074 return (ALint64)self->Padding * 1000000000 / device->Frequency;
1078 static inline void AppendAllDevicesList2(const DevMap *entry)
1079 { AppendAllDevicesList(al_string_get_cstr(entry->name)); }
1080 static inline void AppendCaptureDeviceList2(const DevMap *entry)
1081 { AppendCaptureDeviceList(al_string_get_cstr(entry->name)); }
1083 typedef struct ALCmmdevBackendFactory {
1084 DERIVE_FROM_TYPE(ALCbackendFactory);
1085 } ALCmmdevBackendFactory;
1086 #define ALCMMDEVBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCmmdevBackendFactory, ALCbackendFactory) } }
1088 static ALCboolean ALCmmdevBackendFactory_init(ALCmmdevBackendFactory *self);
1089 static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory *self);
1090 static ALCboolean ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory *self, ALCbackend_Type type);
1091 static void ALCmmdevBackendFactory_probe(ALCmmdevBackendFactory *self, enum DevProbe type);
1092 static ALCbackend* ALCmmdevBackendFactory_createBackend(ALCmmdevBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
1094 DEFINE_ALCBACKENDFACTORY_VTABLE(ALCmmdevBackendFactory);
1097 static BOOL MMDevApiLoad(void)
1099 static HRESULT InitResult;
1100 if(!ThreadHdl)
1102 ThreadRequest req;
1103 InitResult = E_FAIL;
1105 req.FinishedEvt = CreateEvent(NULL, FALSE, FALSE, NULL);
1106 if(req.FinishedEvt == NULL)
1107 ERR("Failed to create event: %lu\n", GetLastError());
1108 else
1110 ThreadHdl = CreateThread(NULL, 0, ALCmmdevProxy_messageHandler, &req, 0, &ThreadID);
1111 if(ThreadHdl != NULL)
1112 InitResult = WaitForResponse(&req);
1113 CloseHandle(req.FinishedEvt);
1116 return SUCCEEDED(InitResult);
1119 static ALCboolean ALCmmdevBackendFactory_init(ALCmmdevBackendFactory* UNUSED(self))
1121 VECTOR_INIT(PlaybackDevices);
1122 VECTOR_INIT(CaptureDevices);
1124 if(!MMDevApiLoad())
1125 return ALC_FALSE;
1126 return ALC_TRUE;
1129 static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory* UNUSED(self))
1131 clear_devlist(&PlaybackDevices);
1132 VECTOR_DEINIT(PlaybackDevices);
1134 clear_devlist(&CaptureDevices);
1135 VECTOR_DEINIT(CaptureDevices);
1137 if(ThreadHdl)
1139 TRACE("Sending WM_QUIT to Thread %04lx\n", ThreadID);
1140 PostThreadMessage(ThreadID, WM_QUIT, 0, 0);
1141 CloseHandle(ThreadHdl);
1142 ThreadHdl = NULL;
1146 static ALCboolean ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory* UNUSED(self), ALCbackend_Type type)
1148 if(type == ALCbackend_Playback)
1149 return ALC_TRUE;
1150 return ALC_FALSE;
1153 static void ALCmmdevBackendFactory_probe(ALCmmdevBackendFactory* UNUSED(self), enum DevProbe type)
1155 ThreadRequest req = { NULL, 0 };
1157 req.FinishedEvt = CreateEvent(NULL, FALSE, FALSE, NULL);
1158 if(req.FinishedEvt == NULL)
1159 ERR("Failed to create event: %lu\n", GetLastError());
1160 else
1162 HRESULT hr = E_FAIL;
1163 if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, type))
1164 hr = WaitForResponse(&req);
1165 if(SUCCEEDED(hr)) switch(type)
1167 case ALL_DEVICE_PROBE:
1168 VECTOR_FOR_EACH(const DevMap, PlaybackDevices, AppendAllDevicesList2);
1169 break;
1171 case CAPTURE_DEVICE_PROBE:
1172 VECTOR_FOR_EACH(const DevMap, CaptureDevices, AppendCaptureDeviceList2);
1173 break;
1175 CloseHandle(req.FinishedEvt);
1176 req.FinishedEvt = NULL;
1180 static ALCbackend* ALCmmdevBackendFactory_createBackend(ALCmmdevBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
1182 if(type == ALCbackend_Playback)
1184 ALCmmdevPlayback *backend;
1186 backend = ALCmmdevPlayback_New(sizeof(*backend));
1187 if(!backend) return NULL;
1188 memset(backend, 0, sizeof(*backend));
1190 ALCmmdevPlayback_Construct(backend, device);
1192 return STATIC_CAST(ALCbackend, backend);
1195 return NULL;
1199 ALCbackendFactory *ALCmmdevBackendFactory_getFactory(void)
1201 static ALCmmdevBackendFactory factory = ALCMMDEVBACKENDFACTORY_INITIALIZER;
1202 return STATIC_CAST(ALCbackendFactory, &factory);