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
28 #include <mmdeviceapi.h>
29 #include <audioclient.h>
31 #include <devpropdef.h>
36 #ifndef _WAVEFORMATEXTENSIBLE_
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)
68 TYPEDEF_VECTOR(DevMap
, vector_DevMap
)
70 static void clear_devlist(vector_DevMap
*list
)
74 iter
= VECTOR_ITER_BEGIN(*list
);
75 end
= VECTOR_ITER_END(*list
);
76 for(;iter
!= end
;iter
++)
78 AL_STRING_DEINIT(iter
->name
);
81 VECTOR_RESIZE(*list
, 0);
84 static vector_DevMap PlaybackDevices
;
85 static vector_DevMap CaptureDevices
;
88 static HANDLE ThreadHdl
;
89 static DWORD ThreadID
;
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
)
108 SetEvent(req
->FinishedEvt
);
111 static HRESULT
WaitForResponse(ThreadRequest
*req
)
113 if(WaitForSingleObject(req
->FinishedEvt
, INFINITE
) == WAIT_OBJECT_0
)
115 ERR("Message response error: %lu\n", GetLastError());
120 static void get_device_name(IMMDevice
*device
, al_string
*name
)
126 hr
= IMMDevice_OpenPropertyStore(device
, STGM_READ
, &ps
);
129 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
133 PropVariantInit(&pvname
);
135 hr
= IPropertyStore_GetValue(ps
, (const PROPERTYKEY
*)&DEVPKEY_Device_FriendlyName
, &pvname
);
137 WARN("GetValue failed: 0x%08lx\n", hr
);
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
)
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
)
162 hr
= IMMDevice_GetId(device
, &devid
);
165 ERR("Failed to get device id: %lx\n", hr
);
172 static HRESULT
probe_devices(IMMDeviceEnumerator
*devenum
, EDataFlow flowdir
, vector_DevMap
*list
)
174 IMMDeviceCollection
*coll
;
175 IMMDevice
*defdev
= NULL
;
176 LPWSTR defdevid
= NULL
;
181 hr
= IMMDeviceEnumerator_EnumAudioEndpoints(devenum
, flowdir
, DEVICE_STATE_ACTIVE
, &coll
);
184 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr
);
189 hr
= IMMDeviceCollection_GetCount(coll
, &count
);
190 if(SUCCEEDED(hr
) && count
> 0)
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
);
206 add_device(defdev
, defdevid
, list
);
209 for(i
= 0;i
< count
;++i
)
214 hr
= IMMDeviceCollection_Item(coll
, i
, &device
);
215 if(FAILED(hr
)) continue;
217 devid
= get_device_id(device
);
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
);
235 /* Proxy interface used by the message handler. */
236 struct ALCmmdevProxyVtable
;
238 typedef struct ALCmmdevProxy
{
239 const struct ALCmmdevProxyVtable
*vtbl
;
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
;
278 TRACE("Starting message thread\n");
280 cohr
= CoInitialize(NULL
);
283 WARN("Failed to initialize COM: 0x%08lx\n", cohr
);
284 ReturnMsgResponse(req
, cohr
);
288 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
291 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr
);
293 ReturnMsgResponse(req
, hr
);
297 IMMDeviceEnumerator_Release(Enumerator
);
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
);
317 case WM_USER_OpenDevice
:
318 req
= (ThreadRequest
*)msg
.wParam
;
319 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
322 if(++deviceCount
== 1)
323 hr
= cohr
= CoInitialize(NULL
);
325 hr
= V0(proxy
,openProxy
)();
328 if(--deviceCount
== 0 && SUCCEEDED(cohr
))
332 ReturnMsgResponse(req
, hr
);
335 case WM_USER_ResetDevice
:
336 req
= (ThreadRequest
*)msg
.wParam
;
337 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
339 hr
= V0(proxy
,resetProxy
)();
340 ReturnMsgResponse(req
, hr
);
343 case WM_USER_StartDevice
:
344 req
= (ThreadRequest
*)msg
.wParam
;
345 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
347 hr
= V0(proxy
,startProxy
)();
348 ReturnMsgResponse(req
, hr
);
351 case WM_USER_StopDevice
:
352 req
= (ThreadRequest
*)msg
.wParam
;
353 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
355 V0(proxy
,stopProxy
)();
356 ReturnMsgResponse(req
, S_OK
);
359 case WM_USER_CloseDevice
:
360 req
= (ThreadRequest
*)msg
.wParam
;
361 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
363 V0(proxy
,closeProxy
)();
364 if(--deviceCount
== 0)
367 ReturnMsgResponse(req
, S_OK
);
370 case WM_USER_Enumerate
:
371 req
= (ThreadRequest
*)msg
.wParam
;
374 if(++deviceCount
== 1)
375 hr
= cohr
= CoInitialize(NULL
);
377 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &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
);
391 if(--deviceCount
== 0 && SUCCEEDED(cohr
))
394 ReturnMsgResponse(req
, hr
);
398 ERR("Unexpected message: %u\n", msg
.message
);
402 TRACE("Message loop finished\n");
408 typedef struct ALCmmdevPlayback
{
409 DERIVE_FROM_TYPE(ALCbackend
);
410 DERIVE_FROM_TYPE(ALCmmdevProxy
);
415 IAudioClient
*client
;
416 IAudioRenderClient
*render
;
421 volatile UINT32 Padding
;
423 volatile int killNow
;
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
));
464 self
->NotifyEvent
= NULL
;
466 self
->MsgEvent
= NULL
;
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
;
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
;
499 hr
= CoInitialize(NULL
);
502 ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr
);
503 ALCdevice_Lock(device
);
504 aluHandleDisconnect(device
);
505 ALCdevice_Unlock(device
);
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
);
519 ERR("Failed to get padding: 0x%08lx\n", hr
);
520 ALCdevice_Lock(device
);
521 aluHandleDisconnect(device
);
522 ALCdevice_Unlock(device
);
525 self
->Padding
= written
;
527 len
= buffer_len
- written
;
528 if(len
< update_size
)
531 res
= WaitForSingleObjectEx(self
->NotifyEvent
, 2000, FALSE
);
532 if(res
!= WAIT_OBJECT_0
)
533 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
536 len
-= len
%update_size
;
538 hr
= IAudioRenderClient_GetBuffer(self
->render
, len
, &buffer
);
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);
549 ERR("Failed to buffer data: 0x%08lx\n", hr
);
550 ALCdevice_Lock(device
);
551 aluHandleDisconnect(device
);
552 ALCdevice_Unlock(device
);
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
)
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
;
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
)
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
;
591 ERR("Unhandled IEEE float channel count: %d\n", out
->Format
.nChannels
);
592 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
596 ERR("Unhandled format tag: 0x%04x\n", in
->wFormatTag
);
603 static ALCenum
ALCmmdevPlayback_open(ALCmmdevPlayback
*self
, const ALCchar
*deviceName
)
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());
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
);
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
);
641 WARN("Failed to find device name matching \"%s\"\n", deviceName
);
647 ThreadRequest req
= { self
->MsgEvent
, 0 };
650 if(PostThreadMessage(ThreadID
, WM_USER_OpenDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
651 hr
= WaitForResponse(&req
);
653 ERR("Failed to post thread message: %lu\n", GetLastError());
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
;
668 ERR("Device init failed: 0x%08lx\n", hr
);
669 return ALC_INVALID_VALUE
;
675 static HRESULT
ALCmmdevPlayback_openProxy(ALCmmdevPlayback
*self
)
677 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
681 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
684 IMMDeviceEnumerator
*Enumerator
= ptr
;
686 hr
= IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator
, eRender
, eMultimedia
, &self
->mmdev
);
688 hr
= IMMDeviceEnumerator_GetDevice(Enumerator
, self
->devid
, &self
->mmdev
);
689 IMMDeviceEnumerator_Release(Enumerator
);
693 hr
= IMMDevice_Activate(self
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
697 get_device_name(self
->mmdev
, &device
->DeviceName
);
703 IMMDevice_Release(self
->mmdev
);
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
;
728 static void ALCmmdevPlayback_closeProxy(ALCmmdevPlayback
*self
)
731 IAudioClient_Release(self
->client
);
735 IMMDevice_Release(self
->mmdev
);
740 static ALCboolean
ALCmmdevPlayback_reset(ALCmmdevPlayback
*self
)
742 ThreadRequest req
= { self
->MsgEvent
, 0 };
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
;
762 IAudioClient_Release(self
->client
);
765 hr
= IMMDevice_Activate(self
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
768 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
773 hr
= IAudioClient_GetMixFormat(self
->client
, &wfx
);
776 ERR("Failed to get mix format: 0x%08lx\n", hr
);
780 if(!MakeExtensible(&OutputType
, wfx
))
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
;
810 ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType
.Format
.nChannels
, OutputType
.dwChannelMask
);
813 switch(device
->FmtChans
)
816 OutputType
.Format
.nChannels
= 1;
817 OutputType
.dwChannelMask
= MONO
;
819 case DevFmtBFormat3D
:
820 device
->FmtChans
= DevFmtStereo
;
823 OutputType
.Format
.nChannels
= 2;
824 OutputType
.dwChannelMask
= STEREO
;
827 OutputType
.Format
.nChannels
= 4;
828 OutputType
.dwChannelMask
= QUAD
;
831 OutputType
.Format
.nChannels
= 6;
832 OutputType
.dwChannelMask
= X5DOT1
;
835 OutputType
.Format
.nChannels
= 6;
836 OutputType
.dwChannelMask
= X5DOT1REAR
;
839 OutputType
.Format
.nChannels
= 7;
840 OutputType
.dwChannelMask
= X6DOT1
;
843 OutputType
.Format
.nChannels
= 8;
844 OutputType
.dwChannelMask
= X7DOT1
;
847 switch(device
->FmtType
)
850 device
->FmtType
= DevFmtUByte
;
853 OutputType
.Format
.wBitsPerSample
= 8;
854 OutputType
.Samples
.wValidBitsPerSample
= 8;
855 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
858 device
->FmtType
= DevFmtShort
;
861 OutputType
.Format
.wBitsPerSample
= 16;
862 OutputType
.Samples
.wValidBitsPerSample
= 16;
863 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
866 device
->FmtType
= DevFmtInt
;
869 OutputType
.Format
.wBitsPerSample
= 32;
870 OutputType
.Samples
.wValidBitsPerSample
= 32;
871 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
874 OutputType
.Format
.wBitsPerSample
= 32;
875 OutputType
.Samples
.wValidBitsPerSample
= 32;
876 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
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
);
889 ERR("Failed to check format support: 0x%08lx\n", hr
);
890 hr
= IAudioClient_GetMixFormat(self
->client
, &wfx
);
894 ERR("Failed to find a supported format: 0x%08lx\n", hr
);
900 if(!MakeExtensible(&OutputType
, wfx
))
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
;
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
;
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;
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
);
967 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
971 hr
= IAudioClient_GetDevicePeriod(self
->client
, &min_per
, NULL
);
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
);
982 ERR("Failed to get audio buffer info: 0x%08lx\n", 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
);
998 ERR("Failed to set event handle: 0x%08lx\n", 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
)
1022 ResetEvent(self
->NotifyEvent
);
1023 hr
= IAudioClient_Start(self
->client
);
1025 ERR("Failed to start audio client: 0x%08lx\n", hr
);
1028 hr
= IAudioClient_GetService(self
->client
, &IID_IAudioRenderClient
, &ptr
);
1033 if(althrd_create(&self
->thread
, ALCmmdevPlayback_mixerProc
, self
) != althrd_success
)
1036 IAudioRenderClient_Release(self
->render
);
1037 self
->render
= NULL
;
1038 IAudioClient_Stop(self
->client
);
1039 ERR("Failed to start thread\n");
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
)
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
;
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());
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
);
1129 static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory
* UNUSED(self
))
1131 clear_devlist(&PlaybackDevices
);
1132 VECTOR_DEINIT(PlaybackDevices
);
1134 clear_devlist(&CaptureDevices
);
1135 VECTOR_DEINIT(CaptureDevices
);
1139 TRACE("Sending WM_QUIT to Thread %04lx\n", ThreadID
);
1140 PostThreadMessage(ThreadID
, WM_QUIT
, 0, 0);
1141 CloseHandle(ThreadHdl
);
1146 static ALCboolean
ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory
* UNUSED(self
), ALCbackend_Type type
)
1148 if(type
== ALCbackend_Playback
)
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());
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
);
1171 case CAPTURE_DEVICE_PROBE
:
1172 VECTOR_FOR_EACH(const DevMap
, CaptureDevices
, AppendCaptureDeviceList2
);
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
);
1199 ALCbackendFactory
*ALCmmdevBackendFactory_getFactory(void)
1201 static ALCmmdevBackendFactory factory
= ALCMMDEVBACKENDFACTORY_INITIALIZER
;
1202 return STATIC_CAST(ALCbackendFactory
, &factory
);