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
29 #include <mmdeviceapi.h>
30 #include <audioclient.h>
32 #include <devpropdef.h>
37 #ifndef _WAVEFORMATEXTENSIBLE_
47 #include "converter.h"
49 #include "backends/base.h"
52 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM
, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
53 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
55 DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName
, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
56 DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor
, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
57 DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID
, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
59 #define MONO SPEAKER_FRONT_CENTER
60 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
61 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
62 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
63 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
64 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
65 #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)
66 #define X7DOT1_WIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_FRONT_LEFT_OF_CENTER|SPEAKER_FRONT_RIGHT_OF_CENTER)
68 #define REFTIME_PER_SEC ((REFERENCE_TIME)10000000)
70 #define DEVNAME_HEAD "OpenAL Soft on "
75 al_string endpoint_guid
; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
78 TYPEDEF_VECTOR(DevMap
, vector_DevMap
)
80 static void clear_devlist(vector_DevMap
*list
)
82 #define CLEAR_DEVMAP(i) do { \
83 AL_STRING_DEINIT((i)->name); \
84 AL_STRING_DEINIT((i)->endpoint_guid); \
88 VECTOR_FOR_EACH(DevMap
, *list
, CLEAR_DEVMAP
);
89 VECTOR_RESIZE(*list
, 0, 0);
93 static vector_DevMap PlaybackDevices
;
94 static vector_DevMap CaptureDevices
;
97 static HANDLE ThreadHdl
;
98 static DWORD ThreadID
;
105 #define WM_USER_First (WM_USER+0)
106 #define WM_USER_OpenDevice (WM_USER+0)
107 #define WM_USER_ResetDevice (WM_USER+1)
108 #define WM_USER_StartDevice (WM_USER+2)
109 #define WM_USER_StopDevice (WM_USER+3)
110 #define WM_USER_CloseDevice (WM_USER+4)
111 #define WM_USER_Enumerate (WM_USER+5)
112 #define WM_USER_Last (WM_USER+5)
114 static const char MessageStr
[WM_USER_Last
+1-WM_USER
][20] = {
123 static inline void ReturnMsgResponse(ThreadRequest
*req
, HRESULT res
)
126 SetEvent(req
->FinishedEvt
);
129 static HRESULT
WaitForResponse(ThreadRequest
*req
)
131 if(WaitForSingleObject(req
->FinishedEvt
, INFINITE
) == WAIT_OBJECT_0
)
133 ERR("Message response error: %lu\n", GetLastError());
138 static void get_device_name_and_guid(IMMDevice
*device
, al_string
*name
, al_string
*guid
)
145 alstr_copy_cstr(name
, DEVNAME_HEAD
);
147 hr
= IMMDevice_OpenPropertyStore(device
, STGM_READ
, &ps
);
150 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
151 alstr_append_cstr(name
, "Unknown Device Name");
152 if(guid
!=NULL
)alstr_copy_cstr(guid
, "Unknown Device GUID");
156 PropVariantInit(&pvname
);
158 hr
= IPropertyStore_GetValue(ps
, (const PROPERTYKEY
*)&DEVPKEY_Device_FriendlyName
, &pvname
);
161 WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr
);
162 alstr_append_cstr(name
, "Unknown Device Name");
164 else if(pvname
.vt
== VT_LPWSTR
)
165 alstr_append_wcstr(name
, pvname
.pwszVal
);
168 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvname
.vt
);
169 alstr_append_cstr(name
, "Unknown Device Name");
171 PropVariantClear(&pvname
);
174 PropVariantInit(&pvguid
);
176 hr
= IPropertyStore_GetValue(ps
, (const PROPERTYKEY
*)&PKEY_AudioEndpoint_GUID
, &pvguid
);
179 WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr
);
180 alstr_copy_cstr(guid
, "Unknown Device GUID");
182 else if(pvguid
.vt
== VT_LPWSTR
)
183 alstr_copy_wcstr(guid
, pvguid
.pwszVal
);
186 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvguid
.vt
);
187 alstr_copy_cstr(guid
, "Unknown Device GUID");
190 PropVariantClear(&pvguid
);
193 IPropertyStore_Release(ps
);
196 static void get_device_formfactor(IMMDevice
*device
, EndpointFormFactor
*formfactor
)
202 hr
= IMMDevice_OpenPropertyStore(device
, STGM_READ
, &ps
);
205 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
209 PropVariantInit(&pvform
);
211 hr
= IPropertyStore_GetValue(ps
, &PKEY_AudioEndpoint_FormFactor
, &pvform
);
213 WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr
);
214 else if(pvform
.vt
== VT_UI4
)
215 *formfactor
= pvform
.ulVal
;
216 else if(pvform
.vt
== VT_EMPTY
)
217 *formfactor
= UnknownFormFactor
;
219 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform
.vt
);
221 PropVariantClear(&pvform
);
222 IPropertyStore_Release(ps
);
226 static void add_device(IMMDevice
*device
, const WCHAR
*devid
, vector_DevMap
*list
)
232 AL_STRING_INIT(tmpname
);
233 AL_STRING_INIT(entry
.name
);
234 AL_STRING_INIT(entry
.endpoint_guid
);
236 entry
.devid
= strdupW(devid
);
237 get_device_name_and_guid(device
, &tmpname
, &entry
.endpoint_guid
);
243 alstr_copy(&entry
.name
, tmpname
);
247 snprintf(str
, sizeof(str
), " #%d", count
+1);
248 alstr_append_cstr(&entry
.name
, str
);
251 #define MATCH_ENTRY(i) (alstr_cmp(entry.name, (i)->name) == 0)
252 VECTOR_FIND_IF(iter
, const DevMap
, *list
, MATCH_ENTRY
);
253 if(iter
== VECTOR_END(*list
)) break;
258 TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", alstr_get_cstr(entry
.name
), alstr_get_cstr(entry
.endpoint_guid
), entry
.devid
);
259 VECTOR_PUSH_BACK(*list
, entry
);
261 AL_STRING_DEINIT(tmpname
);
264 static WCHAR
*get_device_id(IMMDevice
*device
)
269 hr
= IMMDevice_GetId(device
, &devid
);
272 ERR("Failed to get device id: %lx\n", hr
);
279 static HRESULT
probe_devices(IMMDeviceEnumerator
*devenum
, EDataFlow flowdir
, vector_DevMap
*list
)
281 IMMDeviceCollection
*coll
;
282 IMMDevice
*defdev
= NULL
;
283 WCHAR
*defdevid
= NULL
;
288 hr
= IMMDeviceEnumerator_EnumAudioEndpoints(devenum
, flowdir
, DEVICE_STATE_ACTIVE
, &coll
);
291 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr
);
296 hr
= IMMDeviceCollection_GetCount(coll
, &count
);
297 if(SUCCEEDED(hr
) && count
> 0)
300 VECTOR_RESIZE(*list
, 0, count
);
302 hr
= IMMDeviceEnumerator_GetDefaultAudioEndpoint(devenum
, flowdir
,
303 eMultimedia
, &defdev
);
305 if(SUCCEEDED(hr
) && defdev
!= NULL
)
307 defdevid
= get_device_id(defdev
);
309 add_device(defdev
, defdevid
, list
);
312 for(i
= 0;i
< count
;++i
)
317 hr
= IMMDeviceCollection_Item(coll
, i
, &device
);
318 if(FAILED(hr
)) continue;
320 devid
= get_device_id(device
);
323 if(wcscmp(devid
, defdevid
) != 0)
324 add_device(device
, devid
, list
);
325 CoTaskMemFree(devid
);
327 IMMDevice_Release(device
);
330 if(defdev
) IMMDevice_Release(defdev
);
331 if(defdevid
) CoTaskMemFree(defdevid
);
332 IMMDeviceCollection_Release(coll
);
338 /* Proxy interface used by the message handler. */
339 struct ALCmmdevProxyVtable
;
341 typedef struct ALCmmdevProxy
{
342 const struct ALCmmdevProxyVtable
*vtbl
;
345 struct ALCmmdevProxyVtable
{
346 HRESULT (*const openProxy
)(ALCmmdevProxy
*);
347 void (*const closeProxy
)(ALCmmdevProxy
*);
349 HRESULT (*const resetProxy
)(ALCmmdevProxy
*);
350 HRESULT (*const startProxy
)(ALCmmdevProxy
*);
351 void (*const stopProxy
)(ALCmmdevProxy
*);
354 #define DEFINE_ALCMMDEVPROXY_VTABLE(T) \
355 DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, openProxy) \
356 DECLARE_THUNK(T, ALCmmdevProxy, void, closeProxy) \
357 DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, resetProxy) \
358 DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, startProxy) \
359 DECLARE_THUNK(T, ALCmmdevProxy, void, stopProxy) \
361 static const struct ALCmmdevProxyVtable T##_ALCmmdevProxy_vtable = { \
362 T##_ALCmmdevProxy_openProxy, \
363 T##_ALCmmdevProxy_closeProxy, \
364 T##_ALCmmdevProxy_resetProxy, \
365 T##_ALCmmdevProxy_startProxy, \
366 T##_ALCmmdevProxy_stopProxy, \
369 static void ALCmmdevProxy_Construct(ALCmmdevProxy
* UNUSED(self
)) { }
370 static void ALCmmdevProxy_Destruct(ALCmmdevProxy
* UNUSED(self
)) { }
372 static DWORD CALLBACK
ALCmmdevProxy_messageHandler(void *ptr
)
374 ThreadRequest
*req
= ptr
;
375 IMMDeviceEnumerator
*Enumerator
;
376 ALuint deviceCount
= 0;
377 ALCmmdevProxy
*proxy
;
381 TRACE("Starting message thread\n");
383 cohr
= CoInitialize(NULL
);
386 WARN("Failed to initialize COM: 0x%08lx\n", cohr
);
387 ReturnMsgResponse(req
, cohr
);
391 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
394 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr
);
396 ReturnMsgResponse(req
, hr
);
400 IMMDeviceEnumerator_Release(Enumerator
);
405 /* HACK: Force Windows to create a message queue for this thread before
406 * returning success, otherwise PostThreadMessage may fail if it gets
407 * called before GetMessage.
409 PeekMessage(&msg
, NULL
, WM_USER
, WM_USER
, PM_NOREMOVE
);
411 TRACE("Message thread initialization complete\n");
412 ReturnMsgResponse(req
, S_OK
);
414 TRACE("Starting message loop\n");
415 while(GetMessage(&msg
, NULL
, WM_USER_First
, WM_USER_Last
))
417 TRACE("Got message \"%s\" (0x%04x, lparam=%p, wparam=%p)\n",
418 (msg
.message
>= WM_USER
&& msg
.message
<= WM_USER_Last
) ?
419 MessageStr
[msg
.message
-WM_USER
] : "Unknown",
420 msg
.message
, (void*)msg
.lParam
, (void*)msg
.wParam
424 case WM_USER_OpenDevice
:
425 req
= (ThreadRequest
*)msg
.wParam
;
426 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
429 if(++deviceCount
== 1)
430 hr
= cohr
= CoInitialize(NULL
);
432 hr
= V0(proxy
,openProxy
)();
435 if(--deviceCount
== 0 && SUCCEEDED(cohr
))
439 ReturnMsgResponse(req
, hr
);
442 case WM_USER_ResetDevice
:
443 req
= (ThreadRequest
*)msg
.wParam
;
444 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
446 hr
= V0(proxy
,resetProxy
)();
447 ReturnMsgResponse(req
, hr
);
450 case WM_USER_StartDevice
:
451 req
= (ThreadRequest
*)msg
.wParam
;
452 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
454 hr
= V0(proxy
,startProxy
)();
455 ReturnMsgResponse(req
, hr
);
458 case WM_USER_StopDevice
:
459 req
= (ThreadRequest
*)msg
.wParam
;
460 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
462 V0(proxy
,stopProxy
)();
463 ReturnMsgResponse(req
, S_OK
);
466 case WM_USER_CloseDevice
:
467 req
= (ThreadRequest
*)msg
.wParam
;
468 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
470 V0(proxy
,closeProxy
)();
471 if(--deviceCount
== 0)
474 ReturnMsgResponse(req
, S_OK
);
477 case WM_USER_Enumerate
:
478 req
= (ThreadRequest
*)msg
.wParam
;
481 if(++deviceCount
== 1)
482 hr
= cohr
= CoInitialize(NULL
);
484 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
489 if(msg
.lParam
== ALL_DEVICE_PROBE
)
490 hr
= probe_devices(Enumerator
, eRender
, &PlaybackDevices
);
491 else if(msg
.lParam
== CAPTURE_DEVICE_PROBE
)
492 hr
= probe_devices(Enumerator
, eCapture
, &CaptureDevices
);
494 IMMDeviceEnumerator_Release(Enumerator
);
498 if(--deviceCount
== 0 && SUCCEEDED(cohr
))
501 ReturnMsgResponse(req
, hr
);
505 ERR("Unexpected message: %u\n", msg
.message
);
509 TRACE("Message loop finished\n");
515 typedef struct ALCmmdevPlayback
{
516 DERIVE_FROM_TYPE(ALCbackend
);
517 DERIVE_FROM_TYPE(ALCmmdevProxy
);
522 IAudioClient
*client
;
523 IAudioRenderClient
*render
;
528 volatile UINT32 Padding
;
530 volatile int killNow
;
534 static int ALCmmdevPlayback_mixerProc(void *arg
);
536 static void ALCmmdevPlayback_Construct(ALCmmdevPlayback
*self
, ALCdevice
*device
);
537 static void ALCmmdevPlayback_Destruct(ALCmmdevPlayback
*self
);
538 static ALCenum
ALCmmdevPlayback_open(ALCmmdevPlayback
*self
, const ALCchar
*name
);
539 static HRESULT
ALCmmdevPlayback_openProxy(ALCmmdevPlayback
*self
);
540 static void ALCmmdevPlayback_close(ALCmmdevPlayback
*self
);
541 static void ALCmmdevPlayback_closeProxy(ALCmmdevPlayback
*self
);
542 static ALCboolean
ALCmmdevPlayback_reset(ALCmmdevPlayback
*self
);
543 static HRESULT
ALCmmdevPlayback_resetProxy(ALCmmdevPlayback
*self
);
544 static ALCboolean
ALCmmdevPlayback_start(ALCmmdevPlayback
*self
);
545 static HRESULT
ALCmmdevPlayback_startProxy(ALCmmdevPlayback
*self
);
546 static void ALCmmdevPlayback_stop(ALCmmdevPlayback
*self
);
547 static void ALCmmdevPlayback_stopProxy(ALCmmdevPlayback
*self
);
548 static DECLARE_FORWARD2(ALCmmdevPlayback
, ALCbackend
, ALCenum
, captureSamples
, ALCvoid
*, ALCuint
)
549 static DECLARE_FORWARD(ALCmmdevPlayback
, ALCbackend
, ALCuint
, availableSamples
)
550 static ClockLatency
ALCmmdevPlayback_getClockLatency(ALCmmdevPlayback
*self
);
551 static DECLARE_FORWARD(ALCmmdevPlayback
, ALCbackend
, void, lock
)
552 static DECLARE_FORWARD(ALCmmdevPlayback
, ALCbackend
, void, unlock
)
553 DECLARE_DEFAULT_ALLOCATORS(ALCmmdevPlayback
)
555 DEFINE_ALCMMDEVPROXY_VTABLE(ALCmmdevPlayback
);
556 DEFINE_ALCBACKEND_VTABLE(ALCmmdevPlayback
);
559 static void ALCmmdevPlayback_Construct(ALCmmdevPlayback
*self
, ALCdevice
*device
)
561 SET_VTABLE2(ALCmmdevPlayback
, ALCbackend
, self
);
562 SET_VTABLE2(ALCmmdevPlayback
, ALCmmdevProxy
, self
);
563 ALCbackend_Construct(STATIC_CAST(ALCbackend
, self
), device
);
564 ALCmmdevProxy_Construct(STATIC_CAST(ALCmmdevProxy
, self
));
571 self
->NotifyEvent
= NULL
;
573 self
->MsgEvent
= NULL
;
580 static void ALCmmdevPlayback_Destruct(ALCmmdevPlayback
*self
)
582 if(self
->NotifyEvent
!= NULL
)
583 CloseHandle(self
->NotifyEvent
);
584 self
->NotifyEvent
= NULL
;
585 if(self
->MsgEvent
!= NULL
)
586 CloseHandle(self
->MsgEvent
);
587 self
->MsgEvent
= NULL
;
592 ALCmmdevProxy_Destruct(STATIC_CAST(ALCmmdevProxy
, self
));
593 ALCbackend_Destruct(STATIC_CAST(ALCbackend
, self
));
597 FORCE_ALIGN
static int ALCmmdevPlayback_mixerProc(void *arg
)
599 ALCmmdevPlayback
*self
= arg
;
600 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
601 UINT32 buffer_len
, written
;
602 ALuint update_size
, len
;
606 hr
= CoInitialize(NULL
);
609 ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr
);
610 V0(device
->Backend
,lock
)();
611 aluHandleDisconnect(device
);
612 V0(device
->Backend
,unlock
)();
617 althrd_setname(althrd_current(), MIXER_THREAD_NAME
);
619 update_size
= device
->UpdateSize
;
620 buffer_len
= update_size
* device
->NumUpdates
;
621 while(!self
->killNow
)
623 hr
= IAudioClient_GetCurrentPadding(self
->client
, &written
);
626 ERR("Failed to get padding: 0x%08lx\n", hr
);
627 V0(device
->Backend
,lock
)();
628 aluHandleDisconnect(device
);
629 V0(device
->Backend
,unlock
)();
632 self
->Padding
= written
;
634 len
= buffer_len
- written
;
635 if(len
< update_size
)
638 res
= WaitForSingleObjectEx(self
->NotifyEvent
, 2000, FALSE
);
639 if(res
!= WAIT_OBJECT_0
)
640 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
643 len
-= len
%update_size
;
645 hr
= IAudioRenderClient_GetBuffer(self
->render
, len
, &buffer
);
648 ALCmmdevPlayback_lock(self
);
649 aluMixData(device
, buffer
, len
);
650 self
->Padding
= written
+ len
;
651 ALCmmdevPlayback_unlock(self
);
652 hr
= IAudioRenderClient_ReleaseBuffer(self
->render
, len
, 0);
656 ERR("Failed to buffer data: 0x%08lx\n", hr
);
657 V0(device
->Backend
,lock
)();
658 aluHandleDisconnect(device
);
659 V0(device
->Backend
,unlock
)();
670 static ALCboolean
MakeExtensible(WAVEFORMATEXTENSIBLE
*out
, const WAVEFORMATEX
*in
)
672 memset(out
, 0, sizeof(*out
));
673 if(in
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
)
674 *out
= *(const WAVEFORMATEXTENSIBLE
*)in
;
675 else if(in
->wFormatTag
== WAVE_FORMAT_PCM
)
678 out
->Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
679 out
->Format
.cbSize
= sizeof(*out
) - sizeof(*in
);
680 if(out
->Format
.nChannels
== 1)
681 out
->dwChannelMask
= MONO
;
682 else if(out
->Format
.nChannels
== 2)
683 out
->dwChannelMask
= STEREO
;
685 ERR("Unhandled PCM channel count: %d\n", out
->Format
.nChannels
);
686 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
688 else if(in
->wFormatTag
== WAVE_FORMAT_IEEE_FLOAT
)
691 out
->Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
692 out
->Format
.cbSize
= sizeof(*out
) - sizeof(*in
);
693 if(out
->Format
.nChannels
== 1)
694 out
->dwChannelMask
= MONO
;
695 else if(out
->Format
.nChannels
== 2)
696 out
->dwChannelMask
= STEREO
;
698 ERR("Unhandled IEEE float channel count: %d\n", out
->Format
.nChannels
);
699 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
703 ERR("Unhandled format tag: 0x%04x\n", in
->wFormatTag
);
709 static ALCenum
ALCmmdevPlayback_open(ALCmmdevPlayback
*self
, const ALCchar
*deviceName
)
713 self
->NotifyEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
714 self
->MsgEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
715 if(self
->NotifyEvent
== NULL
|| self
->MsgEvent
== NULL
)
717 ERR("Failed to create message events: %lu\n", GetLastError());
727 if(VECTOR_SIZE(PlaybackDevices
) == 0)
729 ThreadRequest req
= { self
->MsgEvent
, 0 };
730 if(PostThreadMessage(ThreadID
, WM_USER_Enumerate
, (WPARAM
)&req
, ALL_DEVICE_PROBE
))
731 (void)WaitForResponse(&req
);
735 #define MATCH_NAME(i) (alstr_cmp_cstr((i)->name, deviceName) == 0 || \
736 alstr_cmp_cstr((i)->endpoint_guid, deviceName) == 0)
737 VECTOR_FIND_IF(iter
, const DevMap
, PlaybackDevices
, MATCH_NAME
);
739 if(iter
== VECTOR_END(PlaybackDevices
))
742 if((len
=MultiByteToWideChar(CP_UTF8
, 0, deviceName
, -1, NULL
, 0)) > 0)
744 WCHAR
*wname
= calloc(sizeof(WCHAR
), len
);
745 MultiByteToWideChar(CP_UTF8
, 0, deviceName
, -1, wname
, len
);
746 #define MATCH_NAME(i) (wcscmp((i)->devid, wname) == 0)
747 VECTOR_FIND_IF(iter
, const DevMap
, PlaybackDevices
, MATCH_NAME
);
752 if(iter
== VECTOR_END(PlaybackDevices
))
753 WARN("Failed to find device name matching \"%s\"\n", deviceName
);
756 ALCdevice
*device
= STATIC_CAST(ALCbackend
,self
)->mDevice
;
757 self
->devid
= strdupW(iter
->devid
);
758 alstr_copy(&device
->DeviceName
, iter
->name
);
766 ThreadRequest req
= { self
->MsgEvent
, 0 };
769 if(PostThreadMessage(ThreadID
, WM_USER_OpenDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
770 hr
= WaitForResponse(&req
);
772 ERR("Failed to post thread message: %lu\n", GetLastError());
777 if(self
->NotifyEvent
!= NULL
)
778 CloseHandle(self
->NotifyEvent
);
779 self
->NotifyEvent
= NULL
;
780 if(self
->MsgEvent
!= NULL
)
781 CloseHandle(self
->MsgEvent
);
782 self
->MsgEvent
= NULL
;
787 ERR("Device init failed: 0x%08lx\n", hr
);
788 return ALC_INVALID_VALUE
;
794 static HRESULT
ALCmmdevPlayback_openProxy(ALCmmdevPlayback
*self
)
796 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
800 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
803 IMMDeviceEnumerator
*Enumerator
= ptr
;
805 hr
= IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator
, eRender
, eMultimedia
, &self
->mmdev
);
807 hr
= IMMDeviceEnumerator_GetDevice(Enumerator
, self
->devid
, &self
->mmdev
);
808 IMMDeviceEnumerator_Release(Enumerator
);
812 hr
= IMMDevice_Activate(self
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
816 if(alstr_empty(device
->DeviceName
))
817 get_device_name_and_guid(self
->mmdev
, &device
->DeviceName
, NULL
);
823 IMMDevice_Release(self
->mmdev
);
831 static void ALCmmdevPlayback_close(ALCmmdevPlayback
*self
)
833 ThreadRequest req
= { self
->MsgEvent
, 0 };
835 if(PostThreadMessage(ThreadID
, WM_USER_CloseDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
836 (void)WaitForResponse(&req
);
838 CloseHandle(self
->MsgEvent
);
839 self
->MsgEvent
= NULL
;
841 CloseHandle(self
->NotifyEvent
);
842 self
->NotifyEvent
= NULL
;
848 static void ALCmmdevPlayback_closeProxy(ALCmmdevPlayback
*self
)
851 IAudioClient_Release(self
->client
);
855 IMMDevice_Release(self
->mmdev
);
860 static ALCboolean
ALCmmdevPlayback_reset(ALCmmdevPlayback
*self
)
862 ThreadRequest req
= { self
->MsgEvent
, 0 };
865 if(PostThreadMessage(ThreadID
, WM_USER_ResetDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
866 hr
= WaitForResponse(&req
);
868 return SUCCEEDED(hr
) ? ALC_TRUE
: ALC_FALSE
;
871 static HRESULT
ALCmmdevPlayback_resetProxy(ALCmmdevPlayback
*self
)
873 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
874 EndpointFormFactor formfactor
= UnknownFormFactor
;
875 WAVEFORMATEXTENSIBLE OutputType
;
876 WAVEFORMATEX
*wfx
= NULL
;
877 REFERENCE_TIME min_per
, buf_time
;
878 UINT32 buffer_len
, min_len
;
883 IAudioClient_Release(self
->client
);
886 hr
= IMMDevice_Activate(self
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
889 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
894 hr
= IAudioClient_GetMixFormat(self
->client
, &wfx
);
897 ERR("Failed to get mix format: 0x%08lx\n", hr
);
901 if(!MakeExtensible(&OutputType
, wfx
))
909 buf_time
= ScaleCeil(device
->UpdateSize
*device
->NumUpdates
, REFTIME_PER_SEC
,
912 if(!(device
->Flags
&DEVICE_FREQUENCY_REQUEST
))
913 device
->Frequency
= OutputType
.Format
.nSamplesPerSec
;
914 if(!(device
->Flags
&DEVICE_CHANNELS_REQUEST
))
916 if(OutputType
.Format
.nChannels
== 1 && OutputType
.dwChannelMask
== MONO
)
917 device
->FmtChans
= DevFmtMono
;
918 else if(OutputType
.Format
.nChannels
== 2 && OutputType
.dwChannelMask
== STEREO
)
919 device
->FmtChans
= DevFmtStereo
;
920 else if(OutputType
.Format
.nChannels
== 4 && OutputType
.dwChannelMask
== QUAD
)
921 device
->FmtChans
= DevFmtQuad
;
922 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1
)
923 device
->FmtChans
= DevFmtX51
;
924 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1REAR
)
925 device
->FmtChans
= DevFmtX51Rear
;
926 else if(OutputType
.Format
.nChannels
== 7 && OutputType
.dwChannelMask
== X6DOT1
)
927 device
->FmtChans
= DevFmtX61
;
928 else if(OutputType
.Format
.nChannels
== 8 && (OutputType
.dwChannelMask
== X7DOT1
|| OutputType
.dwChannelMask
== X7DOT1_WIDE
))
929 device
->FmtChans
= DevFmtX71
;
931 ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType
.Format
.nChannels
, OutputType
.dwChannelMask
);
934 switch(device
->FmtChans
)
937 OutputType
.Format
.nChannels
= 1;
938 OutputType
.dwChannelMask
= MONO
;
941 device
->FmtChans
= DevFmtStereo
;
944 OutputType
.Format
.nChannels
= 2;
945 OutputType
.dwChannelMask
= STEREO
;
948 OutputType
.Format
.nChannels
= 4;
949 OutputType
.dwChannelMask
= QUAD
;
952 OutputType
.Format
.nChannels
= 6;
953 OutputType
.dwChannelMask
= X5DOT1
;
956 OutputType
.Format
.nChannels
= 6;
957 OutputType
.dwChannelMask
= X5DOT1REAR
;
960 OutputType
.Format
.nChannels
= 7;
961 OutputType
.dwChannelMask
= X6DOT1
;
964 OutputType
.Format
.nChannels
= 8;
965 OutputType
.dwChannelMask
= X7DOT1
;
968 switch(device
->FmtType
)
971 device
->FmtType
= DevFmtUByte
;
974 OutputType
.Format
.wBitsPerSample
= 8;
975 OutputType
.Samples
.wValidBitsPerSample
= 8;
976 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
979 device
->FmtType
= DevFmtShort
;
982 OutputType
.Format
.wBitsPerSample
= 16;
983 OutputType
.Samples
.wValidBitsPerSample
= 16;
984 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
987 device
->FmtType
= DevFmtInt
;
990 OutputType
.Format
.wBitsPerSample
= 32;
991 OutputType
.Samples
.wValidBitsPerSample
= 32;
992 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
995 OutputType
.Format
.wBitsPerSample
= 32;
996 OutputType
.Samples
.wValidBitsPerSample
= 32;
997 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
1000 OutputType
.Format
.nSamplesPerSec
= device
->Frequency
;
1002 OutputType
.Format
.nBlockAlign
= OutputType
.Format
.nChannels
*
1003 OutputType
.Format
.wBitsPerSample
/ 8;
1004 OutputType
.Format
.nAvgBytesPerSec
= OutputType
.Format
.nSamplesPerSec
*
1005 OutputType
.Format
.nBlockAlign
;
1007 hr
= IAudioClient_IsFormatSupported(self
->client
, AUDCLNT_SHAREMODE_SHARED
, &OutputType
.Format
, &wfx
);
1010 ERR("Failed to check format support: 0x%08lx\n", hr
);
1011 hr
= IAudioClient_GetMixFormat(self
->client
, &wfx
);
1015 ERR("Failed to find a supported format: 0x%08lx\n", hr
);
1021 if(!MakeExtensible(&OutputType
, wfx
))
1029 device
->Frequency
= OutputType
.Format
.nSamplesPerSec
;
1030 if(OutputType
.Format
.nChannels
== 1 && OutputType
.dwChannelMask
== MONO
)
1031 device
->FmtChans
= DevFmtMono
;
1032 else if(OutputType
.Format
.nChannels
== 2 && OutputType
.dwChannelMask
== STEREO
)
1033 device
->FmtChans
= DevFmtStereo
;
1034 else if(OutputType
.Format
.nChannels
== 4 && OutputType
.dwChannelMask
== QUAD
)
1035 device
->FmtChans
= DevFmtQuad
;
1036 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1
)
1037 device
->FmtChans
= DevFmtX51
;
1038 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1REAR
)
1039 device
->FmtChans
= DevFmtX51Rear
;
1040 else if(OutputType
.Format
.nChannels
== 7 && OutputType
.dwChannelMask
== X6DOT1
)
1041 device
->FmtChans
= DevFmtX61
;
1042 else if(OutputType
.Format
.nChannels
== 8 && (OutputType
.dwChannelMask
== X7DOT1
|| OutputType
.dwChannelMask
== X7DOT1_WIDE
))
1043 device
->FmtChans
= DevFmtX71
;
1046 ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType
.Format
.nChannels
, OutputType
.dwChannelMask
);
1047 device
->FmtChans
= DevFmtStereo
;
1048 OutputType
.Format
.nChannels
= 2;
1049 OutputType
.dwChannelMask
= STEREO
;
1052 if(IsEqualGUID(&OutputType
.SubFormat
, &KSDATAFORMAT_SUBTYPE_PCM
))
1054 if(OutputType
.Format
.wBitsPerSample
== 8)
1055 device
->FmtType
= DevFmtUByte
;
1056 else if(OutputType
.Format
.wBitsPerSample
== 16)
1057 device
->FmtType
= DevFmtShort
;
1058 else if(OutputType
.Format
.wBitsPerSample
== 32)
1059 device
->FmtType
= DevFmtInt
;
1062 device
->FmtType
= DevFmtShort
;
1063 OutputType
.Format
.wBitsPerSample
= 16;
1066 else if(IsEqualGUID(&OutputType
.SubFormat
, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
1068 device
->FmtType
= DevFmtFloat
;
1069 OutputType
.Format
.wBitsPerSample
= 32;
1073 ERR("Unhandled format sub-type\n");
1074 device
->FmtType
= DevFmtShort
;
1075 OutputType
.Format
.wBitsPerSample
= 16;
1076 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1078 OutputType
.Samples
.wValidBitsPerSample
= OutputType
.Format
.wBitsPerSample
;
1080 get_device_formfactor(self
->mmdev
, &formfactor
);
1081 device
->IsHeadphones
= (device
->FmtChans
== DevFmtStereo
&&
1082 (formfactor
== Headphones
|| formfactor
== Headset
)
1085 SetDefaultWFXChannelOrder(device
);
1087 hr
= IAudioClient_Initialize(self
->client
, AUDCLNT_SHAREMODE_SHARED
,
1088 AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
1089 buf_time
, 0, &OutputType
.Format
, NULL
);
1092 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
1096 hr
= IAudioClient_GetDevicePeriod(self
->client
, &min_per
, NULL
);
1099 min_len
= (UINT32
)ScaleCeil(min_per
, device
->Frequency
, REFTIME_PER_SEC
);
1100 /* Find the nearest multiple of the period size to the update size */
1101 if(min_len
< device
->UpdateSize
)
1102 min_len
*= (device
->UpdateSize
+ min_len
/2)/min_len
;
1103 hr
= IAudioClient_GetBufferSize(self
->client
, &buffer_len
);
1107 ERR("Failed to get audio buffer info: 0x%08lx\n", hr
);
1111 device
->UpdateSize
= min_len
;
1112 device
->NumUpdates
= buffer_len
/ device
->UpdateSize
;
1113 if(device
->NumUpdates
<= 1)
1115 ERR("Audio client returned buffer_len < period*2; expect break up\n");
1116 device
->NumUpdates
= 2;
1117 device
->UpdateSize
= buffer_len
/ device
->NumUpdates
;
1120 hr
= IAudioClient_SetEventHandle(self
->client
, self
->NotifyEvent
);
1123 ERR("Failed to set event handle: 0x%08lx\n", hr
);
1131 static ALCboolean
ALCmmdevPlayback_start(ALCmmdevPlayback
*self
)
1133 ThreadRequest req
= { self
->MsgEvent
, 0 };
1134 HRESULT hr
= E_FAIL
;
1136 if(PostThreadMessage(ThreadID
, WM_USER_StartDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1137 hr
= WaitForResponse(&req
);
1139 return SUCCEEDED(hr
) ? ALC_TRUE
: ALC_FALSE
;
1142 static HRESULT
ALCmmdevPlayback_startProxy(ALCmmdevPlayback
*self
)
1147 ResetEvent(self
->NotifyEvent
);
1148 hr
= IAudioClient_Start(self
->client
);
1150 ERR("Failed to start audio client: 0x%08lx\n", hr
);
1153 hr
= IAudioClient_GetService(self
->client
, &IID_IAudioRenderClient
, &ptr
);
1158 if(althrd_create(&self
->thread
, ALCmmdevPlayback_mixerProc
, self
) != althrd_success
)
1161 IAudioRenderClient_Release(self
->render
);
1162 self
->render
= NULL
;
1163 IAudioClient_Stop(self
->client
);
1164 ERR("Failed to start thread\n");
1173 static void ALCmmdevPlayback_stop(ALCmmdevPlayback
*self
)
1175 ThreadRequest req
= { self
->MsgEvent
, 0 };
1176 if(PostThreadMessage(ThreadID
, WM_USER_StopDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1177 (void)WaitForResponse(&req
);
1180 static void ALCmmdevPlayback_stopProxy(ALCmmdevPlayback
*self
)
1188 althrd_join(self
->thread
, &res
);
1190 IAudioRenderClient_Release(self
->render
);
1191 self
->render
= NULL
;
1192 IAudioClient_Stop(self
->client
);
1196 static ClockLatency
ALCmmdevPlayback_getClockLatency(ALCmmdevPlayback
*self
)
1198 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
1201 ALCmmdevPlayback_lock(self
);
1202 ret
.ClockTime
= GetDeviceClockTime(device
);
1203 ret
.Latency
= self
->Padding
* DEVICE_CLOCK_RES
/ device
->Frequency
;
1204 ALCmmdevPlayback_unlock(self
);
1210 typedef struct ALCmmdevCapture
{
1211 DERIVE_FROM_TYPE(ALCbackend
);
1212 DERIVE_FROM_TYPE(ALCmmdevProxy
);
1217 IAudioClient
*client
;
1218 IAudioCaptureClient
*capture
;
1223 ChannelConverter
*ChannelConv
;
1224 SampleConverter
*SampleConv
;
1225 ll_ringbuffer_t
*Ring
;
1227 volatile int killNow
;
1231 static int ALCmmdevCapture_recordProc(void *arg
);
1233 static void ALCmmdevCapture_Construct(ALCmmdevCapture
*self
, ALCdevice
*device
);
1234 static void ALCmmdevCapture_Destruct(ALCmmdevCapture
*self
);
1235 static ALCenum
ALCmmdevCapture_open(ALCmmdevCapture
*self
, const ALCchar
*name
);
1236 static HRESULT
ALCmmdevCapture_openProxy(ALCmmdevCapture
*self
);
1237 static void ALCmmdevCapture_close(ALCmmdevCapture
*self
);
1238 static void ALCmmdevCapture_closeProxy(ALCmmdevCapture
*self
);
1239 static DECLARE_FORWARD(ALCmmdevCapture
, ALCbackend
, ALCboolean
, reset
)
1240 static HRESULT
ALCmmdevCapture_resetProxy(ALCmmdevCapture
*self
);
1241 static ALCboolean
ALCmmdevCapture_start(ALCmmdevCapture
*self
);
1242 static HRESULT
ALCmmdevCapture_startProxy(ALCmmdevCapture
*self
);
1243 static void ALCmmdevCapture_stop(ALCmmdevCapture
*self
);
1244 static void ALCmmdevCapture_stopProxy(ALCmmdevCapture
*self
);
1245 static ALCenum
ALCmmdevCapture_captureSamples(ALCmmdevCapture
*self
, ALCvoid
*buffer
, ALCuint samples
);
1246 static ALuint
ALCmmdevCapture_availableSamples(ALCmmdevCapture
*self
);
1247 static DECLARE_FORWARD(ALCmmdevCapture
, ALCbackend
, ClockLatency
, getClockLatency
)
1248 static DECLARE_FORWARD(ALCmmdevCapture
, ALCbackend
, void, lock
)
1249 static DECLARE_FORWARD(ALCmmdevCapture
, ALCbackend
, void, unlock
)
1250 DECLARE_DEFAULT_ALLOCATORS(ALCmmdevCapture
)
1252 DEFINE_ALCMMDEVPROXY_VTABLE(ALCmmdevCapture
);
1253 DEFINE_ALCBACKEND_VTABLE(ALCmmdevCapture
);
1256 static void ALCmmdevCapture_Construct(ALCmmdevCapture
*self
, ALCdevice
*device
)
1258 SET_VTABLE2(ALCmmdevCapture
, ALCbackend
, self
);
1259 SET_VTABLE2(ALCmmdevCapture
, ALCmmdevProxy
, self
);
1260 ALCbackend_Construct(STATIC_CAST(ALCbackend
, self
), device
);
1261 ALCmmdevProxy_Construct(STATIC_CAST(ALCmmdevProxy
, self
));
1266 self
->client
= NULL
;
1267 self
->capture
= NULL
;
1268 self
->NotifyEvent
= NULL
;
1270 self
->MsgEvent
= NULL
;
1272 self
->ChannelConv
= NULL
;
1273 self
->SampleConv
= NULL
;
1279 static void ALCmmdevCapture_Destruct(ALCmmdevCapture
*self
)
1281 ll_ringbuffer_free(self
->Ring
);
1284 DestroySampleConverter(&self
->SampleConv
);
1285 DestroyChannelConverter(&self
->ChannelConv
);
1287 if(self
->NotifyEvent
!= NULL
)
1288 CloseHandle(self
->NotifyEvent
);
1289 self
->NotifyEvent
= NULL
;
1290 if(self
->MsgEvent
!= NULL
)
1291 CloseHandle(self
->MsgEvent
);
1292 self
->MsgEvent
= NULL
;
1297 ALCmmdevProxy_Destruct(STATIC_CAST(ALCmmdevProxy
, self
));
1298 ALCbackend_Destruct(STATIC_CAST(ALCbackend
, self
));
1302 FORCE_ALIGN
int ALCmmdevCapture_recordProc(void *arg
)
1304 ALCmmdevCapture
*self
= arg
;
1305 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
1306 ALfloat
*samples
= NULL
;
1307 size_t samplesmax
= 0;
1310 hr
= CoInitialize(NULL
);
1313 ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr
);
1314 V0(device
->Backend
,lock
)();
1315 aluHandleDisconnect(device
);
1316 V0(device
->Backend
,unlock
)();
1320 althrd_setname(althrd_current(), RECORD_THREAD_NAME
);
1322 while(!self
->killNow
)
1327 hr
= IAudioCaptureClient_GetNextPacketSize(self
->capture
, &avail
);
1329 ERR("Failed to get next packet size: 0x%08lx\n", hr
);
1336 hr
= IAudioCaptureClient_GetBuffer(self
->capture
,
1337 &rdata
, &numsamples
, &flags
, NULL
, NULL
1340 ERR("Failed to get capture buffer: 0x%08lx\n", hr
);
1343 ll_ringbuffer_data_t data
[2];
1344 size_t dstframes
= 0;
1346 if(self
->ChannelConv
)
1348 if(samplesmax
< numsamples
)
1350 size_t newmax
= RoundUp(numsamples
, 4096);
1351 ALfloat
*tmp
= al_calloc(DEF_ALIGN
, newmax
*2*sizeof(ALfloat
));
1354 samplesmax
= newmax
;
1356 ChannelConverterInput(self
->ChannelConv
, rdata
, samples
, numsamples
);
1357 rdata
= (BYTE
*)samples
;
1360 ll_ringbuffer_get_write_vector(self
->Ring
, data
);
1362 if(self
->SampleConv
)
1364 const ALvoid
*srcdata
= rdata
;
1365 ALsizei srcframes
= numsamples
;
1367 dstframes
= SampleConverterInput(self
->SampleConv
,
1368 &srcdata
, &srcframes
, data
[0].buf
, data
[0].len
1370 if(srcframes
> 0 && dstframes
== data
[0].len
&& data
[1].len
> 0)
1372 /* If some source samples remain, all of the first dest
1373 * block was filled, and there's space in the second
1374 * dest block, do another run for the second block.
1376 dstframes
+= SampleConverterInput(self
->SampleConv
,
1377 &srcdata
, &srcframes
, data
[1].buf
, data
[1].len
1383 size_t framesize
= FrameSizeFromDevFmt(device
->FmtChans
, device
->FmtType
,
1385 ALuint len1
= minu(data
[0].len
, numsamples
);
1386 ALuint len2
= minu(data
[1].len
, numsamples
-len1
);
1388 memcpy(data
[0].buf
, rdata
, len1
*framesize
);
1390 memcpy(data
[1].buf
, rdata
+len1
*framesize
, len2
*framesize
);
1391 dstframes
= len1
+ len2
;
1394 ll_ringbuffer_write_advance(self
->Ring
, dstframes
);
1396 hr
= IAudioCaptureClient_ReleaseBuffer(self
->capture
, numsamples
);
1397 if(FAILED(hr
)) ERR("Failed to release capture buffer: 0x%08lx\n", hr
);
1403 V0(device
->Backend
,lock
)();
1404 aluHandleDisconnect(device
);
1405 V0(device
->Backend
,unlock
)();
1409 res
= WaitForSingleObjectEx(self
->NotifyEvent
, 2000, FALSE
);
1410 if(res
!= WAIT_OBJECT_0
)
1411 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
1423 static ALCenum
ALCmmdevCapture_open(ALCmmdevCapture
*self
, const ALCchar
*deviceName
)
1427 self
->NotifyEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
1428 self
->MsgEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
1429 if(self
->NotifyEvent
== NULL
|| self
->MsgEvent
== NULL
)
1431 ERR("Failed to create message events: %lu\n", GetLastError());
1441 if(VECTOR_SIZE(CaptureDevices
) == 0)
1443 ThreadRequest req
= { self
->MsgEvent
, 0 };
1444 if(PostThreadMessage(ThreadID
, WM_USER_Enumerate
, (WPARAM
)&req
, CAPTURE_DEVICE_PROBE
))
1445 (void)WaitForResponse(&req
);
1449 #define MATCH_NAME(i) (alstr_cmp_cstr((i)->name, deviceName) == 0 || \
1450 alstr_cmp_cstr((i)->endpoint_guid, deviceName) == 0)
1451 VECTOR_FIND_IF(iter
, const DevMap
, CaptureDevices
, MATCH_NAME
);
1453 if(iter
== VECTOR_END(CaptureDevices
))
1456 if((len
=MultiByteToWideChar(CP_UTF8
, 0, deviceName
, -1, NULL
, 0)) > 0)
1458 WCHAR
*wname
= calloc(sizeof(WCHAR
), len
);
1459 MultiByteToWideChar(CP_UTF8
, 0, deviceName
, -1, wname
, len
);
1460 #define MATCH_NAME(i) (wcscmp((i)->devid, wname) == 0)
1461 VECTOR_FIND_IF(iter
, const DevMap
, CaptureDevices
, MATCH_NAME
);
1466 if(iter
== VECTOR_END(CaptureDevices
))
1467 WARN("Failed to find device name matching \"%s\"\n", deviceName
);
1470 ALCdevice
*device
= STATIC_CAST(ALCbackend
,self
)->mDevice
;
1471 self
->devid
= strdupW(iter
->devid
);
1472 alstr_copy(&device
->DeviceName
, iter
->name
);
1480 ThreadRequest req
= { self
->MsgEvent
, 0 };
1483 if(PostThreadMessage(ThreadID
, WM_USER_OpenDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1484 hr
= WaitForResponse(&req
);
1486 ERR("Failed to post thread message: %lu\n", GetLastError());
1491 if(self
->NotifyEvent
!= NULL
)
1492 CloseHandle(self
->NotifyEvent
);
1493 self
->NotifyEvent
= NULL
;
1494 if(self
->MsgEvent
!= NULL
)
1495 CloseHandle(self
->MsgEvent
);
1496 self
->MsgEvent
= NULL
;
1501 ERR("Device init failed: 0x%08lx\n", hr
);
1502 return ALC_INVALID_VALUE
;
1506 ThreadRequest req
= { self
->MsgEvent
, 0 };
1509 if(PostThreadMessage(ThreadID
, WM_USER_ResetDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1510 hr
= WaitForResponse(&req
);
1512 ERR("Failed to post thread message: %lu\n", GetLastError());
1516 ALCmmdevCapture_close(self
);
1517 if(hr
== E_OUTOFMEMORY
)
1518 return ALC_OUT_OF_MEMORY
;
1519 return ALC_INVALID_VALUE
;
1523 return ALC_NO_ERROR
;
1526 static HRESULT
ALCmmdevCapture_openProxy(ALCmmdevCapture
*self
)
1528 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
1532 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
1535 IMMDeviceEnumerator
*Enumerator
= ptr
;
1537 hr
= IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator
, eCapture
, eMultimedia
, &self
->mmdev
);
1539 hr
= IMMDeviceEnumerator_GetDevice(Enumerator
, self
->devid
, &self
->mmdev
);
1540 IMMDeviceEnumerator_Release(Enumerator
);
1544 hr
= IMMDevice_Activate(self
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
1548 if(alstr_empty(device
->DeviceName
))
1549 get_device_name_and_guid(self
->mmdev
, &device
->DeviceName
, NULL
);
1555 IMMDevice_Release(self
->mmdev
);
1563 static void ALCmmdevCapture_close(ALCmmdevCapture
*self
)
1565 ThreadRequest req
= { self
->MsgEvent
, 0 };
1567 if(PostThreadMessage(ThreadID
, WM_USER_CloseDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1568 (void)WaitForResponse(&req
);
1570 ll_ringbuffer_free(self
->Ring
);
1573 CloseHandle(self
->MsgEvent
);
1574 self
->MsgEvent
= NULL
;
1576 CloseHandle(self
->NotifyEvent
);
1577 self
->NotifyEvent
= NULL
;
1583 static void ALCmmdevCapture_closeProxy(ALCmmdevCapture
*self
)
1586 IAudioClient_Release(self
->client
);
1587 self
->client
= NULL
;
1590 IMMDevice_Release(self
->mmdev
);
1595 static HRESULT
ALCmmdevCapture_resetProxy(ALCmmdevCapture
*self
)
1597 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
1598 WAVEFORMATEXTENSIBLE OutputType
;
1599 WAVEFORMATEX
*wfx
= NULL
;
1600 enum DevFmtType srcType
;
1601 REFERENCE_TIME buf_time
;
1607 IAudioClient_Release(self
->client
);
1608 self
->client
= NULL
;
1610 hr
= IMMDevice_Activate(self
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
1613 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
1618 buf_time
= ScaleCeil(device
->UpdateSize
*device
->NumUpdates
, REFTIME_PER_SEC
,
1620 // Make sure buffer is at least 100ms in size
1621 buf_time
= maxu64(buf_time
, REFTIME_PER_SEC
/10);
1622 device
->UpdateSize
= (ALuint
)ScaleCeil(buf_time
, device
->Frequency
, REFTIME_PER_SEC
) /
1625 OutputType
.Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
1626 switch(device
->FmtChans
)
1629 OutputType
.Format
.nChannels
= 1;
1630 OutputType
.dwChannelMask
= MONO
;
1633 OutputType
.Format
.nChannels
= 2;
1634 OutputType
.dwChannelMask
= STEREO
;
1637 OutputType
.Format
.nChannels
= 4;
1638 OutputType
.dwChannelMask
= QUAD
;
1641 OutputType
.Format
.nChannels
= 6;
1642 OutputType
.dwChannelMask
= X5DOT1
;
1645 OutputType
.Format
.nChannels
= 6;
1646 OutputType
.dwChannelMask
= X5DOT1REAR
;
1649 OutputType
.Format
.nChannels
= 7;
1650 OutputType
.dwChannelMask
= X6DOT1
;
1653 OutputType
.Format
.nChannels
= 8;
1654 OutputType
.dwChannelMask
= X7DOT1
;
1660 switch(device
->FmtType
)
1662 /* NOTE: Signedness doesn't matter, the converter will handle it. */
1665 OutputType
.Format
.wBitsPerSample
= 8;
1666 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1670 OutputType
.Format
.wBitsPerSample
= 16;
1671 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1675 OutputType
.Format
.wBitsPerSample
= 32;
1676 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1679 OutputType
.Format
.wBitsPerSample
= 32;
1680 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
1683 OutputType
.Samples
.wValidBitsPerSample
= OutputType
.Format
.wBitsPerSample
;
1684 OutputType
.Format
.nSamplesPerSec
= device
->Frequency
;
1686 OutputType
.Format
.nBlockAlign
= OutputType
.Format
.nChannels
*
1687 OutputType
.Format
.wBitsPerSample
/ 8;
1688 OutputType
.Format
.nAvgBytesPerSec
= OutputType
.Format
.nSamplesPerSec
*
1689 OutputType
.Format
.nBlockAlign
;
1690 OutputType
.Format
.cbSize
= sizeof(OutputType
) - sizeof(OutputType
.Format
);
1692 hr
= IAudioClient_IsFormatSupported(self
->client
,
1693 AUDCLNT_SHAREMODE_SHARED
, &OutputType
.Format
, &wfx
1697 ERR("Failed to check format support: 0x%08lx\n", hr
);
1701 DestroySampleConverter(&self
->SampleConv
);
1702 DestroyChannelConverter(&self
->ChannelConv
);
1706 if(!(wfx
->nChannels
== OutputType
.Format
.nChannels
||
1707 (wfx
->nChannels
== 1 && OutputType
.Format
.nChannels
== 2) ||
1708 (wfx
->nChannels
== 2 && OutputType
.Format
.nChannels
== 1)))
1710 ERR("Failed to get matching format, wanted: %s %s %uhz, got: %d channel%s %d-bit %luhz\n",
1711 DevFmtChannelsString(device
->FmtChans
), DevFmtTypeString(device
->FmtType
),
1712 device
->Frequency
, wfx
->nChannels
, (wfx
->nChannels
==1)?"":"s", wfx
->wBitsPerSample
,
1713 wfx
->nSamplesPerSec
);
1718 if(!MakeExtensible(&OutputType
, wfx
))
1727 if(IsEqualGUID(&OutputType
.SubFormat
, &KSDATAFORMAT_SUBTYPE_PCM
))
1729 if(OutputType
.Format
.wBitsPerSample
== 8)
1730 srcType
= DevFmtUByte
;
1731 else if(OutputType
.Format
.wBitsPerSample
== 16)
1732 srcType
= DevFmtShort
;
1733 else if(OutputType
.Format
.wBitsPerSample
== 32)
1734 srcType
= DevFmtInt
;
1737 ERR("Unhandled integer bit depth: %d\n", OutputType
.Format
.wBitsPerSample
);
1741 else if(IsEqualGUID(&OutputType
.SubFormat
, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
1743 if(OutputType
.Format
.wBitsPerSample
== 32)
1744 srcType
= DevFmtFloat
;
1747 ERR("Unhandled float bit depth: %d\n", OutputType
.Format
.wBitsPerSample
);
1753 ERR("Unhandled format sub-type\n");
1757 if(device
->FmtChans
== DevFmtMono
&& OutputType
.Format
.nChannels
== 2)
1759 self
->ChannelConv
= CreateChannelConverter(srcType
, DevFmtStereo
,
1761 if(!self
->ChannelConv
)
1763 ERR("Failed to create %s stereo-to-mono converter\n", DevFmtTypeString(srcType
));
1766 TRACE("Created %s stereo-to-mono converter\n", DevFmtTypeString(srcType
));
1767 /* The channel converter always outputs float, so change the input type
1768 * for the resampler/type-converter.
1770 srcType
= DevFmtFloat
;
1772 else if(device
->FmtChans
== DevFmtStereo
&& OutputType
.Format
.nChannels
== 1)
1774 self
->ChannelConv
= CreateChannelConverter(srcType
, DevFmtMono
,
1776 if(!self
->ChannelConv
)
1778 ERR("Failed to create %s mono-to-stereo converter\n", DevFmtTypeString(srcType
));
1781 TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType
));
1782 srcType
= DevFmtFloat
;
1785 if(device
->Frequency
!= OutputType
.Format
.nSamplesPerSec
|| device
->FmtType
!= srcType
)
1787 self
->SampleConv
= CreateSampleConverter(
1788 srcType
, device
->FmtType
, ChannelsFromDevFmt(device
->FmtChans
, device
->AmbiOrder
),
1789 OutputType
.Format
.nSamplesPerSec
, device
->Frequency
1791 if(!self
->SampleConv
)
1793 ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1794 DevFmtChannelsString(device
->FmtChans
), DevFmtTypeString(device
->FmtType
),
1795 device
->Frequency
, DevFmtTypeString(srcType
), OutputType
.Format
.nSamplesPerSec
);
1798 TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1799 DevFmtChannelsString(device
->FmtChans
), DevFmtTypeString(device
->FmtType
),
1800 device
->Frequency
, DevFmtTypeString(srcType
), OutputType
.Format
.nSamplesPerSec
);
1803 hr
= IAudioClient_Initialize(self
->client
,
1804 AUDCLNT_SHAREMODE_SHARED
, AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
1805 buf_time
, 0, &OutputType
.Format
, NULL
1809 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
1813 hr
= IAudioClient_GetBufferSize(self
->client
, &buffer_len
);
1816 ERR("Failed to get buffer size: 0x%08lx\n", hr
);
1820 buffer_len
= maxu(device
->UpdateSize
*device
->NumUpdates
+ 1, buffer_len
);
1821 ll_ringbuffer_free(self
->Ring
);
1822 self
->Ring
= ll_ringbuffer_create(buffer_len
,
1823 FrameSizeFromDevFmt(device
->FmtChans
, device
->FmtType
, device
->AmbiOrder
)
1827 ERR("Failed to allocate capture ring buffer\n");
1828 return E_OUTOFMEMORY
;
1831 hr
= IAudioClient_SetEventHandle(self
->client
, self
->NotifyEvent
);
1834 ERR("Failed to set event handle: 0x%08lx\n", hr
);
1842 static ALCboolean
ALCmmdevCapture_start(ALCmmdevCapture
*self
)
1844 ThreadRequest req
= { self
->MsgEvent
, 0 };
1845 HRESULT hr
= E_FAIL
;
1847 if(PostThreadMessage(ThreadID
, WM_USER_StartDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1848 hr
= WaitForResponse(&req
);
1850 return SUCCEEDED(hr
) ? ALC_TRUE
: ALC_FALSE
;
1853 static HRESULT
ALCmmdevCapture_startProxy(ALCmmdevCapture
*self
)
1858 ResetEvent(self
->NotifyEvent
);
1859 hr
= IAudioClient_Start(self
->client
);
1862 ERR("Failed to start audio client: 0x%08lx\n", hr
);
1866 hr
= IAudioClient_GetService(self
->client
, &IID_IAudioCaptureClient
, &ptr
);
1869 self
->capture
= ptr
;
1871 if(althrd_create(&self
->thread
, ALCmmdevCapture_recordProc
, self
) != althrd_success
)
1873 ERR("Failed to start thread\n");
1874 IAudioCaptureClient_Release(self
->capture
);
1875 self
->capture
= NULL
;
1882 IAudioClient_Stop(self
->client
);
1883 IAudioClient_Reset(self
->client
);
1890 static void ALCmmdevCapture_stop(ALCmmdevCapture
*self
)
1892 ThreadRequest req
= { self
->MsgEvent
, 0 };
1893 if(PostThreadMessage(ThreadID
, WM_USER_StopDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1894 (void)WaitForResponse(&req
);
1897 static void ALCmmdevCapture_stopProxy(ALCmmdevCapture
*self
)
1905 althrd_join(self
->thread
, &res
);
1907 IAudioCaptureClient_Release(self
->capture
);
1908 self
->capture
= NULL
;
1909 IAudioClient_Stop(self
->client
);
1910 IAudioClient_Reset(self
->client
);
1914 ALuint
ALCmmdevCapture_availableSamples(ALCmmdevCapture
*self
)
1916 return (ALuint
)ll_ringbuffer_read_space(self
->Ring
);
1919 ALCenum
ALCmmdevCapture_captureSamples(ALCmmdevCapture
*self
, ALCvoid
*buffer
, ALCuint samples
)
1921 if(ALCmmdevCapture_availableSamples(self
) < samples
)
1922 return ALC_INVALID_VALUE
;
1923 ll_ringbuffer_read(self
->Ring
, buffer
, samples
);
1924 return ALC_NO_ERROR
;
1928 static inline void AppendAllDevicesList2(const DevMap
*entry
)
1929 { AppendAllDevicesList(alstr_get_cstr(entry
->name
)); }
1930 static inline void AppendCaptureDeviceList2(const DevMap
*entry
)
1931 { AppendCaptureDeviceList(alstr_get_cstr(entry
->name
)); }
1933 typedef struct ALCmmdevBackendFactory
{
1934 DERIVE_FROM_TYPE(ALCbackendFactory
);
1935 } ALCmmdevBackendFactory
;
1936 #define ALCMMDEVBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCmmdevBackendFactory, ALCbackendFactory) } }
1938 static ALCboolean
ALCmmdevBackendFactory_init(ALCmmdevBackendFactory
*self
);
1939 static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory
*self
);
1940 static ALCboolean
ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory
*self
, ALCbackend_Type type
);
1941 static void ALCmmdevBackendFactory_probe(ALCmmdevBackendFactory
*self
, enum DevProbe type
);
1942 static ALCbackend
* ALCmmdevBackendFactory_createBackend(ALCmmdevBackendFactory
*self
, ALCdevice
*device
, ALCbackend_Type type
);
1944 DEFINE_ALCBACKENDFACTORY_VTABLE(ALCmmdevBackendFactory
);
1947 static BOOL
MMDevApiLoad(void)
1949 static HRESULT InitResult
;
1953 InitResult
= E_FAIL
;
1955 req
.FinishedEvt
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
1956 if(req
.FinishedEvt
== NULL
)
1957 ERR("Failed to create event: %lu\n", GetLastError());
1960 ThreadHdl
= CreateThread(NULL
, 0, ALCmmdevProxy_messageHandler
, &req
, 0, &ThreadID
);
1961 if(ThreadHdl
!= NULL
)
1962 InitResult
= WaitForResponse(&req
);
1963 CloseHandle(req
.FinishedEvt
);
1966 return SUCCEEDED(InitResult
);
1969 static ALCboolean
ALCmmdevBackendFactory_init(ALCmmdevBackendFactory
* UNUSED(self
))
1971 VECTOR_INIT(PlaybackDevices
);
1972 VECTOR_INIT(CaptureDevices
);
1979 static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory
* UNUSED(self
))
1981 clear_devlist(&PlaybackDevices
);
1982 VECTOR_DEINIT(PlaybackDevices
);
1984 clear_devlist(&CaptureDevices
);
1985 VECTOR_DEINIT(CaptureDevices
);
1989 TRACE("Sending WM_QUIT to Thread %04lx\n", ThreadID
);
1990 PostThreadMessage(ThreadID
, WM_QUIT
, 0, 0);
1991 CloseHandle(ThreadHdl
);
1996 static ALCboolean
ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory
* UNUSED(self
), ALCbackend_Type type
)
1998 /* TODO: Disable capture with mmdevapi for now, since it doesn't do any
1999 * rechanneling or resampling; if the device is configured for 48000hz
2000 * stereo input, for example, and the app asks for 22050hz mono,
2001 * initialization will fail.
2003 if(type
== ALCbackend_Playback
|| type
== ALCbackend_Capture
)
2008 static void ALCmmdevBackendFactory_probe(ALCmmdevBackendFactory
* UNUSED(self
), enum DevProbe type
)
2010 ThreadRequest req
= { NULL
, 0 };
2012 req
.FinishedEvt
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
2013 if(req
.FinishedEvt
== NULL
)
2014 ERR("Failed to create event: %lu\n", GetLastError());
2017 HRESULT hr
= E_FAIL
;
2018 if(PostThreadMessage(ThreadID
, WM_USER_Enumerate
, (WPARAM
)&req
, type
))
2019 hr
= WaitForResponse(&req
);
2020 if(SUCCEEDED(hr
)) switch(type
)
2022 case ALL_DEVICE_PROBE
:
2023 VECTOR_FOR_EACH(const DevMap
, PlaybackDevices
, AppendAllDevicesList2
);
2026 case CAPTURE_DEVICE_PROBE
:
2027 VECTOR_FOR_EACH(const DevMap
, CaptureDevices
, AppendCaptureDeviceList2
);
2030 CloseHandle(req
.FinishedEvt
);
2031 req
.FinishedEvt
= NULL
;
2035 static ALCbackend
* ALCmmdevBackendFactory_createBackend(ALCmmdevBackendFactory
* UNUSED(self
), ALCdevice
*device
, ALCbackend_Type type
)
2037 if(type
== ALCbackend_Playback
)
2039 ALCmmdevPlayback
*backend
;
2040 NEW_OBJ(backend
, ALCmmdevPlayback
)(device
);
2041 if(!backend
) return NULL
;
2042 return STATIC_CAST(ALCbackend
, backend
);
2044 if(type
== ALCbackend_Capture
)
2046 ALCmmdevCapture
*backend
;
2047 NEW_OBJ(backend
, ALCmmdevCapture
)(device
);
2048 if(!backend
) return NULL
;
2049 return STATIC_CAST(ALCbackend
, backend
);
2056 ALCbackendFactory
*ALCmmdevBackendFactory_getFactory(void)
2058 static ALCmmdevBackendFactory factory
= ALCMMDEVBACKENDFACTORY_INITIALIZER
;
2059 return STATIC_CAST(ALCbackendFactory
, &factory
);