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 DEVNAME_HEAD "OpenAL Soft on "
73 al_string endpoint_guid
; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
76 TYPEDEF_VECTOR(DevMap
, vector_DevMap
)
78 static void clear_devlist(vector_DevMap
*list
)
80 #define CLEAR_DEVMAP(i) do { \
81 AL_STRING_DEINIT((i)->name); \
82 AL_STRING_DEINIT((i)->endpoint_guid); \
86 VECTOR_FOR_EACH(DevMap
, *list
, CLEAR_DEVMAP
);
87 VECTOR_RESIZE(*list
, 0, 0);
91 static vector_DevMap PlaybackDevices
;
92 static vector_DevMap CaptureDevices
;
95 static HANDLE ThreadHdl
;
96 static DWORD ThreadID
;
103 #define WM_USER_First (WM_USER+0)
104 #define WM_USER_OpenDevice (WM_USER+0)
105 #define WM_USER_ResetDevice (WM_USER+1)
106 #define WM_USER_StartDevice (WM_USER+2)
107 #define WM_USER_StopDevice (WM_USER+3)
108 #define WM_USER_CloseDevice (WM_USER+4)
109 #define WM_USER_Enumerate (WM_USER+5)
110 #define WM_USER_Last (WM_USER+5)
112 static inline void ReturnMsgResponse(ThreadRequest
*req
, HRESULT res
)
115 SetEvent(req
->FinishedEvt
);
118 static HRESULT
WaitForResponse(ThreadRequest
*req
)
120 if(WaitForSingleObject(req
->FinishedEvt
, INFINITE
) == WAIT_OBJECT_0
)
122 ERR("Message response error: %lu\n", GetLastError());
127 static void get_device_name_and_guid(IMMDevice
*device
, al_string
*name
, al_string
*guid
)
134 alstr_copy_cstr(name
, DEVNAME_HEAD
);
136 hr
= IMMDevice_OpenPropertyStore(device
, STGM_READ
, &ps
);
139 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
140 alstr_append_cstr(name
, "Unknown Device Name");
141 if(guid
!=NULL
)alstr_copy_cstr(guid
, "Unknown Device GUID");
145 PropVariantInit(&pvname
);
147 hr
= IPropertyStore_GetValue(ps
, (const PROPERTYKEY
*)&DEVPKEY_Device_FriendlyName
, &pvname
);
150 WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr
);
151 alstr_append_cstr(name
, "Unknown Device Name");
153 else if(pvname
.vt
== VT_LPWSTR
)
154 alstr_append_wcstr(name
, pvname
.pwszVal
);
157 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvname
.vt
);
158 alstr_append_cstr(name
, "Unknown Device Name");
160 PropVariantClear(&pvname
);
163 PropVariantInit(&pvguid
);
165 hr
= IPropertyStore_GetValue(ps
, (const PROPERTYKEY
*)&PKEY_AudioEndpoint_GUID
, &pvguid
);
168 WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr
);
169 alstr_copy_cstr(guid
, "Unknown Device GUID");
171 else if(pvguid
.vt
== VT_LPWSTR
)
172 alstr_copy_wcstr(guid
, pvguid
.pwszVal
);
175 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvguid
.vt
);
176 alstr_copy_cstr(guid
, "Unknown Device GUID");
179 PropVariantClear(&pvguid
);
182 IPropertyStore_Release(ps
);
185 static void get_device_formfactor(IMMDevice
*device
, EndpointFormFactor
*formfactor
)
191 hr
= IMMDevice_OpenPropertyStore(device
, STGM_READ
, &ps
);
194 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
198 PropVariantInit(&pvform
);
200 hr
= IPropertyStore_GetValue(ps
, &PKEY_AudioEndpoint_FormFactor
, &pvform
);
202 WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr
);
203 else if(pvform
.vt
== VT_UI4
)
204 *formfactor
= pvform
.ulVal
;
205 else if(pvform
.vt
== VT_EMPTY
)
206 *formfactor
= UnknownFormFactor
;
208 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform
.vt
);
210 PropVariantClear(&pvform
);
211 IPropertyStore_Release(ps
);
215 static void add_device(IMMDevice
*device
, const WCHAR
*devid
, vector_DevMap
*list
)
221 AL_STRING_INIT(tmpname
);
222 AL_STRING_INIT(entry
.name
);
223 AL_STRING_INIT(entry
.endpoint_guid
);
225 entry
.devid
= strdupW(devid
);
226 get_device_name_and_guid(device
, &tmpname
, &entry
.endpoint_guid
);
232 alstr_copy(&entry
.name
, tmpname
);
236 snprintf(str
, sizeof(str
), " #%d", count
+1);
237 alstr_append_cstr(&entry
.name
, str
);
240 #define MATCH_ENTRY(i) (alstr_cmp(entry.name, (i)->name) == 0)
241 VECTOR_FIND_IF(iter
, const DevMap
, *list
, MATCH_ENTRY
);
242 if(iter
== VECTOR_END(*list
)) break;
247 TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", alstr_get_cstr(entry
.name
), alstr_get_cstr(entry
.endpoint_guid
), entry
.devid
);
248 VECTOR_PUSH_BACK(*list
, entry
);
250 AL_STRING_DEINIT(tmpname
);
253 static WCHAR
*get_device_id(IMMDevice
*device
)
258 hr
= IMMDevice_GetId(device
, &devid
);
261 ERR("Failed to get device id: %lx\n", hr
);
268 static HRESULT
probe_devices(IMMDeviceEnumerator
*devenum
, EDataFlow flowdir
, vector_DevMap
*list
)
270 IMMDeviceCollection
*coll
;
271 IMMDevice
*defdev
= NULL
;
272 WCHAR
*defdevid
= NULL
;
277 hr
= IMMDeviceEnumerator_EnumAudioEndpoints(devenum
, flowdir
, DEVICE_STATE_ACTIVE
, &coll
);
280 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr
);
285 hr
= IMMDeviceCollection_GetCount(coll
, &count
);
286 if(SUCCEEDED(hr
) && count
> 0)
289 VECTOR_RESIZE(*list
, 0, count
);
291 hr
= IMMDeviceEnumerator_GetDefaultAudioEndpoint(devenum
, flowdir
,
292 eMultimedia
, &defdev
);
294 if(SUCCEEDED(hr
) && defdev
!= NULL
)
296 defdevid
= get_device_id(defdev
);
298 add_device(defdev
, defdevid
, list
);
301 for(i
= 0;i
< count
;++i
)
306 hr
= IMMDeviceCollection_Item(coll
, i
, &device
);
307 if(FAILED(hr
)) continue;
309 devid
= get_device_id(device
);
312 if(wcscmp(devid
, defdevid
) != 0)
313 add_device(device
, devid
, list
);
314 CoTaskMemFree(devid
);
316 IMMDevice_Release(device
);
319 if(defdev
) IMMDevice_Release(defdev
);
320 if(defdevid
) CoTaskMemFree(defdevid
);
321 IMMDeviceCollection_Release(coll
);
327 /* Proxy interface used by the message handler. */
328 struct ALCmmdevProxyVtable
;
330 typedef struct ALCmmdevProxy
{
331 const struct ALCmmdevProxyVtable
*vtbl
;
334 struct ALCmmdevProxyVtable
{
335 HRESULT (*const openProxy
)(ALCmmdevProxy
*);
336 void (*const closeProxy
)(ALCmmdevProxy
*);
338 HRESULT (*const resetProxy
)(ALCmmdevProxy
*);
339 HRESULT (*const startProxy
)(ALCmmdevProxy
*);
340 void (*const stopProxy
)(ALCmmdevProxy
*);
343 #define DEFINE_ALCMMDEVPROXY_VTABLE(T) \
344 DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, openProxy) \
345 DECLARE_THUNK(T, ALCmmdevProxy, void, closeProxy) \
346 DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, resetProxy) \
347 DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, startProxy) \
348 DECLARE_THUNK(T, ALCmmdevProxy, void, stopProxy) \
350 static const struct ALCmmdevProxyVtable T##_ALCmmdevProxy_vtable = { \
351 T##_ALCmmdevProxy_openProxy, \
352 T##_ALCmmdevProxy_closeProxy, \
353 T##_ALCmmdevProxy_resetProxy, \
354 T##_ALCmmdevProxy_startProxy, \
355 T##_ALCmmdevProxy_stopProxy, \
358 static void ALCmmdevProxy_Construct(ALCmmdevProxy
* UNUSED(self
)) { }
359 static void ALCmmdevProxy_Destruct(ALCmmdevProxy
* UNUSED(self
)) { }
361 static DWORD CALLBACK
ALCmmdevProxy_messageHandler(void *ptr
)
363 ThreadRequest
*req
= ptr
;
364 IMMDeviceEnumerator
*Enumerator
;
365 ALuint deviceCount
= 0;
366 ALCmmdevProxy
*proxy
;
370 TRACE("Starting message thread\n");
372 cohr
= CoInitialize(NULL
);
375 WARN("Failed to initialize COM: 0x%08lx\n", cohr
);
376 ReturnMsgResponse(req
, cohr
);
380 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
383 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr
);
385 ReturnMsgResponse(req
, hr
);
389 IMMDeviceEnumerator_Release(Enumerator
);
394 /* HACK: Force Windows to create a message queue for this thread before
395 * returning success, otherwise PostThreadMessage may fail if it gets
396 * called before GetMessage.
398 PeekMessage(&msg
, NULL
, WM_USER
, WM_USER
, PM_NOREMOVE
);
400 TRACE("Message thread initialization complete\n");
401 ReturnMsgResponse(req
, S_OK
);
403 TRACE("Starting message loop\n");
404 while(GetMessage(&msg
, NULL
, WM_USER_First
, WM_USER_Last
))
406 TRACE("Got message %u (lparam=%p, wparam=%p)\n", msg
.message
, (void*)msg
.lParam
, (void*)msg
.wParam
);
409 case WM_USER_OpenDevice
:
410 req
= (ThreadRequest
*)msg
.wParam
;
411 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
414 if(++deviceCount
== 1)
415 hr
= cohr
= CoInitialize(NULL
);
417 hr
= V0(proxy
,openProxy
)();
420 if(--deviceCount
== 0 && SUCCEEDED(cohr
))
424 ReturnMsgResponse(req
, hr
);
427 case WM_USER_ResetDevice
:
428 req
= (ThreadRequest
*)msg
.wParam
;
429 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
431 hr
= V0(proxy
,resetProxy
)();
432 ReturnMsgResponse(req
, hr
);
435 case WM_USER_StartDevice
:
436 req
= (ThreadRequest
*)msg
.wParam
;
437 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
439 hr
= V0(proxy
,startProxy
)();
440 ReturnMsgResponse(req
, hr
);
443 case WM_USER_StopDevice
:
444 req
= (ThreadRequest
*)msg
.wParam
;
445 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
447 V0(proxy
,stopProxy
)();
448 ReturnMsgResponse(req
, S_OK
);
451 case WM_USER_CloseDevice
:
452 req
= (ThreadRequest
*)msg
.wParam
;
453 proxy
= (ALCmmdevProxy
*)msg
.lParam
;
455 V0(proxy
,closeProxy
)();
456 if(--deviceCount
== 0)
459 ReturnMsgResponse(req
, S_OK
);
462 case WM_USER_Enumerate
:
463 req
= (ThreadRequest
*)msg
.wParam
;
466 if(++deviceCount
== 1)
467 hr
= cohr
= CoInitialize(NULL
);
469 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
474 if(msg
.lParam
== ALL_DEVICE_PROBE
)
475 hr
= probe_devices(Enumerator
, eRender
, &PlaybackDevices
);
476 else if(msg
.lParam
== CAPTURE_DEVICE_PROBE
)
477 hr
= probe_devices(Enumerator
, eCapture
, &CaptureDevices
);
479 IMMDeviceEnumerator_Release(Enumerator
);
483 if(--deviceCount
== 0 && SUCCEEDED(cohr
))
486 ReturnMsgResponse(req
, hr
);
490 ERR("Unexpected message: %u\n", msg
.message
);
494 TRACE("Message loop finished\n");
500 typedef struct ALCmmdevPlayback
{
501 DERIVE_FROM_TYPE(ALCbackend
);
502 DERIVE_FROM_TYPE(ALCmmdevProxy
);
507 IAudioClient
*client
;
508 IAudioRenderClient
*render
;
513 volatile UINT32 Padding
;
515 volatile int killNow
;
519 static int ALCmmdevPlayback_mixerProc(void *arg
);
521 static void ALCmmdevPlayback_Construct(ALCmmdevPlayback
*self
, ALCdevice
*device
);
522 static void ALCmmdevPlayback_Destruct(ALCmmdevPlayback
*self
);
523 static ALCenum
ALCmmdevPlayback_open(ALCmmdevPlayback
*self
, const ALCchar
*name
);
524 static HRESULT
ALCmmdevPlayback_openProxy(ALCmmdevPlayback
*self
);
525 static void ALCmmdevPlayback_close(ALCmmdevPlayback
*self
);
526 static void ALCmmdevPlayback_closeProxy(ALCmmdevPlayback
*self
);
527 static ALCboolean
ALCmmdevPlayback_reset(ALCmmdevPlayback
*self
);
528 static HRESULT
ALCmmdevPlayback_resetProxy(ALCmmdevPlayback
*self
);
529 static ALCboolean
ALCmmdevPlayback_start(ALCmmdevPlayback
*self
);
530 static HRESULT
ALCmmdevPlayback_startProxy(ALCmmdevPlayback
*self
);
531 static void ALCmmdevPlayback_stop(ALCmmdevPlayback
*self
);
532 static void ALCmmdevPlayback_stopProxy(ALCmmdevPlayback
*self
);
533 static DECLARE_FORWARD2(ALCmmdevPlayback
, ALCbackend
, ALCenum
, captureSamples
, ALCvoid
*, ALCuint
)
534 static DECLARE_FORWARD(ALCmmdevPlayback
, ALCbackend
, ALCuint
, availableSamples
)
535 static ClockLatency
ALCmmdevPlayback_getClockLatency(ALCmmdevPlayback
*self
);
536 static DECLARE_FORWARD(ALCmmdevPlayback
, ALCbackend
, void, lock
)
537 static DECLARE_FORWARD(ALCmmdevPlayback
, ALCbackend
, void, unlock
)
538 DECLARE_DEFAULT_ALLOCATORS(ALCmmdevPlayback
)
540 DEFINE_ALCMMDEVPROXY_VTABLE(ALCmmdevPlayback
);
541 DEFINE_ALCBACKEND_VTABLE(ALCmmdevPlayback
);
544 static void ALCmmdevPlayback_Construct(ALCmmdevPlayback
*self
, ALCdevice
*device
)
546 SET_VTABLE2(ALCmmdevPlayback
, ALCbackend
, self
);
547 SET_VTABLE2(ALCmmdevPlayback
, ALCmmdevProxy
, self
);
548 ALCbackend_Construct(STATIC_CAST(ALCbackend
, self
), device
);
549 ALCmmdevProxy_Construct(STATIC_CAST(ALCmmdevProxy
, self
));
556 self
->NotifyEvent
= NULL
;
558 self
->MsgEvent
= NULL
;
565 static void ALCmmdevPlayback_Destruct(ALCmmdevPlayback
*self
)
567 if(self
->NotifyEvent
!= NULL
)
568 CloseHandle(self
->NotifyEvent
);
569 self
->NotifyEvent
= NULL
;
570 if(self
->MsgEvent
!= NULL
)
571 CloseHandle(self
->MsgEvent
);
572 self
->MsgEvent
= NULL
;
577 ALCmmdevProxy_Destruct(STATIC_CAST(ALCmmdevProxy
, self
));
578 ALCbackend_Destruct(STATIC_CAST(ALCbackend
, self
));
582 FORCE_ALIGN
static int ALCmmdevPlayback_mixerProc(void *arg
)
584 ALCmmdevPlayback
*self
= arg
;
585 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
586 UINT32 buffer_len
, written
;
587 ALuint update_size
, len
;
591 hr
= CoInitialize(NULL
);
594 ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr
);
595 V0(device
->Backend
,lock
)();
596 aluHandleDisconnect(device
);
597 V0(device
->Backend
,unlock
)();
602 althrd_setname(althrd_current(), MIXER_THREAD_NAME
);
604 update_size
= device
->UpdateSize
;
605 buffer_len
= update_size
* device
->NumUpdates
;
606 while(!self
->killNow
)
608 hr
= IAudioClient_GetCurrentPadding(self
->client
, &written
);
611 ERR("Failed to get padding: 0x%08lx\n", hr
);
612 V0(device
->Backend
,lock
)();
613 aluHandleDisconnect(device
);
614 V0(device
->Backend
,unlock
)();
617 self
->Padding
= written
;
619 len
= buffer_len
- written
;
620 if(len
< update_size
)
623 res
= WaitForSingleObjectEx(self
->NotifyEvent
, 2000, FALSE
);
624 if(res
!= WAIT_OBJECT_0
)
625 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
628 len
-= len
%update_size
;
630 hr
= IAudioRenderClient_GetBuffer(self
->render
, len
, &buffer
);
633 ALCmmdevPlayback_lock(self
);
634 aluMixData(device
, buffer
, len
);
635 self
->Padding
= written
+ len
;
636 ALCmmdevPlayback_unlock(self
);
637 hr
= IAudioRenderClient_ReleaseBuffer(self
->render
, len
, 0);
641 ERR("Failed to buffer data: 0x%08lx\n", hr
);
642 V0(device
->Backend
,lock
)();
643 aluHandleDisconnect(device
);
644 V0(device
->Backend
,unlock
)();
655 static ALCboolean
MakeExtensible(WAVEFORMATEXTENSIBLE
*out
, const WAVEFORMATEX
*in
)
657 memset(out
, 0, sizeof(*out
));
658 if(in
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
)
659 *out
= *(const WAVEFORMATEXTENSIBLE
*)in
;
660 else if(in
->wFormatTag
== WAVE_FORMAT_PCM
)
663 out
->Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
664 out
->Format
.cbSize
= sizeof(*out
) - sizeof(*in
);
665 if(out
->Format
.nChannels
== 1)
666 out
->dwChannelMask
= MONO
;
667 else if(out
->Format
.nChannels
== 2)
668 out
->dwChannelMask
= STEREO
;
670 ERR("Unhandled PCM channel count: %d\n", out
->Format
.nChannels
);
671 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
673 else if(in
->wFormatTag
== WAVE_FORMAT_IEEE_FLOAT
)
676 out
->Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
677 out
->Format
.cbSize
= sizeof(*out
) - sizeof(*in
);
678 if(out
->Format
.nChannels
== 1)
679 out
->dwChannelMask
= MONO
;
680 else if(out
->Format
.nChannels
== 2)
681 out
->dwChannelMask
= STEREO
;
683 ERR("Unhandled IEEE float channel count: %d\n", out
->Format
.nChannels
);
684 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
688 ERR("Unhandled format tag: 0x%04x\n", in
->wFormatTag
);
694 static ALCenum
ALCmmdevPlayback_open(ALCmmdevPlayback
*self
, const ALCchar
*deviceName
)
698 self
->NotifyEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
699 self
->MsgEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
700 if(self
->NotifyEvent
== NULL
|| self
->MsgEvent
== NULL
)
702 ERR("Failed to create message events: %lu\n", GetLastError());
712 if(VECTOR_SIZE(PlaybackDevices
) == 0)
714 ThreadRequest req
= { self
->MsgEvent
, 0 };
715 if(PostThreadMessage(ThreadID
, WM_USER_Enumerate
, (WPARAM
)&req
, ALL_DEVICE_PROBE
))
716 (void)WaitForResponse(&req
);
720 #define MATCH_NAME(i) (alstr_cmp_cstr((i)->name, deviceName) == 0 || \
721 alstr_cmp_cstr((i)->endpoint_guid, deviceName) == 0)
722 VECTOR_FIND_IF(iter
, const DevMap
, PlaybackDevices
, MATCH_NAME
);
724 if(iter
== VECTOR_END(PlaybackDevices
))
727 if((len
=MultiByteToWideChar(CP_UTF8
, 0, deviceName
, -1, NULL
, 0)) > 0)
729 WCHAR
*wname
= calloc(sizeof(WCHAR
), len
);
730 MultiByteToWideChar(CP_UTF8
, 0, deviceName
, -1, wname
, len
);
731 #define MATCH_NAME(i) (wcscmp((i)->devid, wname) == 0)
732 VECTOR_FIND_IF(iter
, const DevMap
, PlaybackDevices
, MATCH_NAME
);
737 if(iter
== VECTOR_END(PlaybackDevices
))
738 WARN("Failed to find device name matching \"%s\"\n", deviceName
);
741 ALCdevice
*device
= STATIC_CAST(ALCbackend
,self
)->mDevice
;
742 self
->devid
= strdupW(iter
->devid
);
743 alstr_copy(&device
->DeviceName
, iter
->name
);
751 ThreadRequest req
= { self
->MsgEvent
, 0 };
754 if(PostThreadMessage(ThreadID
, WM_USER_OpenDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
755 hr
= WaitForResponse(&req
);
757 ERR("Failed to post thread message: %lu\n", GetLastError());
762 if(self
->NotifyEvent
!= NULL
)
763 CloseHandle(self
->NotifyEvent
);
764 self
->NotifyEvent
= NULL
;
765 if(self
->MsgEvent
!= NULL
)
766 CloseHandle(self
->MsgEvent
);
767 self
->MsgEvent
= NULL
;
772 ERR("Device init failed: 0x%08lx\n", hr
);
773 return ALC_INVALID_VALUE
;
779 static HRESULT
ALCmmdevPlayback_openProxy(ALCmmdevPlayback
*self
)
781 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
785 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
788 IMMDeviceEnumerator
*Enumerator
= ptr
;
790 hr
= IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator
, eRender
, eMultimedia
, &self
->mmdev
);
792 hr
= IMMDeviceEnumerator_GetDevice(Enumerator
, self
->devid
, &self
->mmdev
);
793 IMMDeviceEnumerator_Release(Enumerator
);
797 hr
= IMMDevice_Activate(self
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
801 if(alstr_empty(device
->DeviceName
))
802 get_device_name_and_guid(self
->mmdev
, &device
->DeviceName
, NULL
);
808 IMMDevice_Release(self
->mmdev
);
816 static void ALCmmdevPlayback_close(ALCmmdevPlayback
*self
)
818 ThreadRequest req
= { self
->MsgEvent
, 0 };
820 if(PostThreadMessage(ThreadID
, WM_USER_CloseDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
821 (void)WaitForResponse(&req
);
823 CloseHandle(self
->MsgEvent
);
824 self
->MsgEvent
= NULL
;
826 CloseHandle(self
->NotifyEvent
);
827 self
->NotifyEvent
= NULL
;
833 static void ALCmmdevPlayback_closeProxy(ALCmmdevPlayback
*self
)
836 IAudioClient_Release(self
->client
);
840 IMMDevice_Release(self
->mmdev
);
845 static ALCboolean
ALCmmdevPlayback_reset(ALCmmdevPlayback
*self
)
847 ThreadRequest req
= { self
->MsgEvent
, 0 };
850 if(PostThreadMessage(ThreadID
, WM_USER_ResetDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
851 hr
= WaitForResponse(&req
);
853 return SUCCEEDED(hr
) ? ALC_TRUE
: ALC_FALSE
;
856 static HRESULT
ALCmmdevPlayback_resetProxy(ALCmmdevPlayback
*self
)
858 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
859 EndpointFormFactor formfactor
= UnknownFormFactor
;
860 WAVEFORMATEXTENSIBLE OutputType
;
861 WAVEFORMATEX
*wfx
= NULL
;
862 REFERENCE_TIME min_per
, buf_time
;
863 UINT32 buffer_len
, min_len
;
868 IAudioClient_Release(self
->client
);
871 hr
= IMMDevice_Activate(self
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
874 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
879 hr
= IAudioClient_GetMixFormat(self
->client
, &wfx
);
882 ERR("Failed to get mix format: 0x%08lx\n", hr
);
886 if(!MakeExtensible(&OutputType
, wfx
))
894 buf_time
= ((REFERENCE_TIME
)device
->UpdateSize
*device
->NumUpdates
*10000000 +
895 device
->Frequency
-1) / device
->Frequency
;
897 if(!(device
->Flags
&DEVICE_FREQUENCY_REQUEST
))
898 device
->Frequency
= OutputType
.Format
.nSamplesPerSec
;
899 if(!(device
->Flags
&DEVICE_CHANNELS_REQUEST
))
901 if(OutputType
.Format
.nChannels
== 1 && OutputType
.dwChannelMask
== MONO
)
902 device
->FmtChans
= DevFmtMono
;
903 else if(OutputType
.Format
.nChannels
== 2 && OutputType
.dwChannelMask
== STEREO
)
904 device
->FmtChans
= DevFmtStereo
;
905 else if(OutputType
.Format
.nChannels
== 4 && OutputType
.dwChannelMask
== QUAD
)
906 device
->FmtChans
= DevFmtQuad
;
907 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1
)
908 device
->FmtChans
= DevFmtX51
;
909 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1REAR
)
910 device
->FmtChans
= DevFmtX51Rear
;
911 else if(OutputType
.Format
.nChannels
== 7 && OutputType
.dwChannelMask
== X6DOT1
)
912 device
->FmtChans
= DevFmtX61
;
913 else if(OutputType
.Format
.nChannels
== 8 && (OutputType
.dwChannelMask
== X7DOT1
|| OutputType
.dwChannelMask
== X7DOT1_WIDE
))
914 device
->FmtChans
= DevFmtX71
;
916 ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType
.Format
.nChannels
, OutputType
.dwChannelMask
);
919 switch(device
->FmtChans
)
922 OutputType
.Format
.nChannels
= 1;
923 OutputType
.dwChannelMask
= MONO
;
926 device
->FmtChans
= DevFmtStereo
;
929 OutputType
.Format
.nChannels
= 2;
930 OutputType
.dwChannelMask
= STEREO
;
933 OutputType
.Format
.nChannels
= 4;
934 OutputType
.dwChannelMask
= QUAD
;
937 OutputType
.Format
.nChannels
= 6;
938 OutputType
.dwChannelMask
= X5DOT1
;
941 OutputType
.Format
.nChannels
= 6;
942 OutputType
.dwChannelMask
= X5DOT1REAR
;
945 OutputType
.Format
.nChannels
= 7;
946 OutputType
.dwChannelMask
= X6DOT1
;
949 OutputType
.Format
.nChannels
= 8;
950 OutputType
.dwChannelMask
= X7DOT1
;
953 switch(device
->FmtType
)
956 device
->FmtType
= DevFmtUByte
;
959 OutputType
.Format
.wBitsPerSample
= 8;
960 OutputType
.Samples
.wValidBitsPerSample
= 8;
961 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
964 device
->FmtType
= DevFmtShort
;
967 OutputType
.Format
.wBitsPerSample
= 16;
968 OutputType
.Samples
.wValidBitsPerSample
= 16;
969 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
972 device
->FmtType
= DevFmtInt
;
975 OutputType
.Format
.wBitsPerSample
= 32;
976 OutputType
.Samples
.wValidBitsPerSample
= 32;
977 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
980 OutputType
.Format
.wBitsPerSample
= 32;
981 OutputType
.Samples
.wValidBitsPerSample
= 32;
982 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
985 OutputType
.Format
.nSamplesPerSec
= device
->Frequency
;
987 OutputType
.Format
.nBlockAlign
= OutputType
.Format
.nChannels
*
988 OutputType
.Format
.wBitsPerSample
/ 8;
989 OutputType
.Format
.nAvgBytesPerSec
= OutputType
.Format
.nSamplesPerSec
*
990 OutputType
.Format
.nBlockAlign
;
992 hr
= IAudioClient_IsFormatSupported(self
->client
, AUDCLNT_SHAREMODE_SHARED
, &OutputType
.Format
, &wfx
);
995 ERR("Failed to check format support: 0x%08lx\n", hr
);
996 hr
= IAudioClient_GetMixFormat(self
->client
, &wfx
);
1000 ERR("Failed to find a supported format: 0x%08lx\n", hr
);
1006 if(!MakeExtensible(&OutputType
, wfx
))
1014 device
->Frequency
= OutputType
.Format
.nSamplesPerSec
;
1015 if(OutputType
.Format
.nChannels
== 1 && OutputType
.dwChannelMask
== MONO
)
1016 device
->FmtChans
= DevFmtMono
;
1017 else if(OutputType
.Format
.nChannels
== 2 && OutputType
.dwChannelMask
== STEREO
)
1018 device
->FmtChans
= DevFmtStereo
;
1019 else if(OutputType
.Format
.nChannels
== 4 && OutputType
.dwChannelMask
== QUAD
)
1020 device
->FmtChans
= DevFmtQuad
;
1021 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1
)
1022 device
->FmtChans
= DevFmtX51
;
1023 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1REAR
)
1024 device
->FmtChans
= DevFmtX51Rear
;
1025 else if(OutputType
.Format
.nChannels
== 7 && OutputType
.dwChannelMask
== X6DOT1
)
1026 device
->FmtChans
= DevFmtX61
;
1027 else if(OutputType
.Format
.nChannels
== 8 && (OutputType
.dwChannelMask
== X7DOT1
|| OutputType
.dwChannelMask
== X7DOT1_WIDE
))
1028 device
->FmtChans
= DevFmtX71
;
1031 ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType
.Format
.nChannels
, OutputType
.dwChannelMask
);
1032 device
->FmtChans
= DevFmtStereo
;
1033 OutputType
.Format
.nChannels
= 2;
1034 OutputType
.dwChannelMask
= STEREO
;
1037 if(IsEqualGUID(&OutputType
.SubFormat
, &KSDATAFORMAT_SUBTYPE_PCM
))
1039 if(OutputType
.Format
.wBitsPerSample
== 8)
1040 device
->FmtType
= DevFmtUByte
;
1041 else if(OutputType
.Format
.wBitsPerSample
== 16)
1042 device
->FmtType
= DevFmtShort
;
1043 else if(OutputType
.Format
.wBitsPerSample
== 32)
1044 device
->FmtType
= DevFmtInt
;
1047 device
->FmtType
= DevFmtShort
;
1048 OutputType
.Format
.wBitsPerSample
= 16;
1051 else if(IsEqualGUID(&OutputType
.SubFormat
, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
1053 device
->FmtType
= DevFmtFloat
;
1054 OutputType
.Format
.wBitsPerSample
= 32;
1058 ERR("Unhandled format sub-type\n");
1059 device
->FmtType
= DevFmtShort
;
1060 OutputType
.Format
.wBitsPerSample
= 16;
1061 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1063 OutputType
.Samples
.wValidBitsPerSample
= OutputType
.Format
.wBitsPerSample
;
1065 get_device_formfactor(self
->mmdev
, &formfactor
);
1066 device
->IsHeadphones
= (device
->FmtChans
== DevFmtStereo
&&
1067 (formfactor
== Headphones
|| formfactor
== Headset
)
1070 SetDefaultWFXChannelOrder(device
);
1072 hr
= IAudioClient_Initialize(self
->client
, AUDCLNT_SHAREMODE_SHARED
,
1073 AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
1074 buf_time
, 0, &OutputType
.Format
, NULL
);
1077 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
1081 hr
= IAudioClient_GetDevicePeriod(self
->client
, &min_per
, NULL
);
1084 min_len
= (UINT32
)((min_per
*device
->Frequency
+ 10000000-1) / 10000000);
1085 /* Find the nearest multiple of the period size to the update size */
1086 if(min_len
< device
->UpdateSize
)
1087 min_len
*= (device
->UpdateSize
+ min_len
/2)/min_len
;
1088 hr
= IAudioClient_GetBufferSize(self
->client
, &buffer_len
);
1092 ERR("Failed to get audio buffer info: 0x%08lx\n", hr
);
1096 device
->UpdateSize
= min_len
;
1097 device
->NumUpdates
= buffer_len
/ device
->UpdateSize
;
1098 if(device
->NumUpdates
<= 1)
1100 ERR("Audio client returned buffer_len < period*2; expect break up\n");
1101 device
->NumUpdates
= 2;
1102 device
->UpdateSize
= buffer_len
/ device
->NumUpdates
;
1105 hr
= IAudioClient_SetEventHandle(self
->client
, self
->NotifyEvent
);
1108 ERR("Failed to set event handle: 0x%08lx\n", hr
);
1116 static ALCboolean
ALCmmdevPlayback_start(ALCmmdevPlayback
*self
)
1118 ThreadRequest req
= { self
->MsgEvent
, 0 };
1119 HRESULT hr
= E_FAIL
;
1121 if(PostThreadMessage(ThreadID
, WM_USER_StartDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1122 hr
= WaitForResponse(&req
);
1124 return SUCCEEDED(hr
) ? ALC_TRUE
: ALC_FALSE
;
1127 static HRESULT
ALCmmdevPlayback_startProxy(ALCmmdevPlayback
*self
)
1132 ResetEvent(self
->NotifyEvent
);
1133 hr
= IAudioClient_Start(self
->client
);
1135 ERR("Failed to start audio client: 0x%08lx\n", hr
);
1138 hr
= IAudioClient_GetService(self
->client
, &IID_IAudioRenderClient
, &ptr
);
1143 if(althrd_create(&self
->thread
, ALCmmdevPlayback_mixerProc
, self
) != althrd_success
)
1146 IAudioRenderClient_Release(self
->render
);
1147 self
->render
= NULL
;
1148 IAudioClient_Stop(self
->client
);
1149 ERR("Failed to start thread\n");
1158 static void ALCmmdevPlayback_stop(ALCmmdevPlayback
*self
)
1160 ThreadRequest req
= { self
->MsgEvent
, 0 };
1161 if(PostThreadMessage(ThreadID
, WM_USER_StopDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1162 (void)WaitForResponse(&req
);
1165 static void ALCmmdevPlayback_stopProxy(ALCmmdevPlayback
*self
)
1173 althrd_join(self
->thread
, &res
);
1175 IAudioRenderClient_Release(self
->render
);
1176 self
->render
= NULL
;
1177 IAudioClient_Stop(self
->client
);
1181 static ClockLatency
ALCmmdevPlayback_getClockLatency(ALCmmdevPlayback
*self
)
1183 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
1186 ALCmmdevPlayback_lock(self
);
1187 ret
.ClockTime
= GetDeviceClockTime(device
);
1188 ret
.Latency
= self
->Padding
* DEVICE_CLOCK_RES
/ device
->Frequency
;
1189 ALCmmdevPlayback_unlock(self
);
1195 typedef struct ALCmmdevCapture
{
1196 DERIVE_FROM_TYPE(ALCbackend
);
1197 DERIVE_FROM_TYPE(ALCmmdevProxy
);
1202 IAudioClient
*client
;
1203 IAudioCaptureClient
*capture
;
1208 ChannelConverter
*ChannelConv
;
1209 SampleConverter
*SampleConv
;
1210 ll_ringbuffer_t
*Ring
;
1212 volatile int killNow
;
1216 static int ALCmmdevCapture_recordProc(void *arg
);
1218 static void ALCmmdevCapture_Construct(ALCmmdevCapture
*self
, ALCdevice
*device
);
1219 static void ALCmmdevCapture_Destruct(ALCmmdevCapture
*self
);
1220 static ALCenum
ALCmmdevCapture_open(ALCmmdevCapture
*self
, const ALCchar
*name
);
1221 static HRESULT
ALCmmdevCapture_openProxy(ALCmmdevCapture
*self
);
1222 static void ALCmmdevCapture_close(ALCmmdevCapture
*self
);
1223 static void ALCmmdevCapture_closeProxy(ALCmmdevCapture
*self
);
1224 static DECLARE_FORWARD(ALCmmdevCapture
, ALCbackend
, ALCboolean
, reset
)
1225 static HRESULT
ALCmmdevCapture_resetProxy(ALCmmdevCapture
*self
);
1226 static ALCboolean
ALCmmdevCapture_start(ALCmmdevCapture
*self
);
1227 static HRESULT
ALCmmdevCapture_startProxy(ALCmmdevCapture
*self
);
1228 static void ALCmmdevCapture_stop(ALCmmdevCapture
*self
);
1229 static void ALCmmdevCapture_stopProxy(ALCmmdevCapture
*self
);
1230 static ALCenum
ALCmmdevCapture_captureSamples(ALCmmdevCapture
*self
, ALCvoid
*buffer
, ALCuint samples
);
1231 static ALuint
ALCmmdevCapture_availableSamples(ALCmmdevCapture
*self
);
1232 static DECLARE_FORWARD(ALCmmdevCapture
, ALCbackend
, ClockLatency
, getClockLatency
)
1233 static DECLARE_FORWARD(ALCmmdevCapture
, ALCbackend
, void, lock
)
1234 static DECLARE_FORWARD(ALCmmdevCapture
, ALCbackend
, void, unlock
)
1235 DECLARE_DEFAULT_ALLOCATORS(ALCmmdevCapture
)
1237 DEFINE_ALCMMDEVPROXY_VTABLE(ALCmmdevCapture
);
1238 DEFINE_ALCBACKEND_VTABLE(ALCmmdevCapture
);
1241 static void ALCmmdevCapture_Construct(ALCmmdevCapture
*self
, ALCdevice
*device
)
1243 SET_VTABLE2(ALCmmdevCapture
, ALCbackend
, self
);
1244 SET_VTABLE2(ALCmmdevCapture
, ALCmmdevProxy
, self
);
1245 ALCbackend_Construct(STATIC_CAST(ALCbackend
, self
), device
);
1246 ALCmmdevProxy_Construct(STATIC_CAST(ALCmmdevProxy
, self
));
1251 self
->client
= NULL
;
1252 self
->capture
= NULL
;
1253 self
->NotifyEvent
= NULL
;
1255 self
->MsgEvent
= NULL
;
1257 self
->ChannelConv
= NULL
;
1258 self
->SampleConv
= NULL
;
1264 static void ALCmmdevCapture_Destruct(ALCmmdevCapture
*self
)
1266 ll_ringbuffer_free(self
->Ring
);
1269 DestroySampleConverter(&self
->SampleConv
);
1270 DestroyChannelConverter(&self
->ChannelConv
);
1272 if(self
->NotifyEvent
!= NULL
)
1273 CloseHandle(self
->NotifyEvent
);
1274 self
->NotifyEvent
= NULL
;
1275 if(self
->MsgEvent
!= NULL
)
1276 CloseHandle(self
->MsgEvent
);
1277 self
->MsgEvent
= NULL
;
1282 ALCmmdevProxy_Destruct(STATIC_CAST(ALCmmdevProxy
, self
));
1283 ALCbackend_Destruct(STATIC_CAST(ALCbackend
, self
));
1287 FORCE_ALIGN
int ALCmmdevCapture_recordProc(void *arg
)
1289 ALCmmdevCapture
*self
= arg
;
1290 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
1291 ALfloat
*samples
= NULL
;
1292 size_t samplesmax
= 0;
1295 hr
= CoInitialize(NULL
);
1298 ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr
);
1299 V0(device
->Backend
,lock
)();
1300 aluHandleDisconnect(device
);
1301 V0(device
->Backend
,unlock
)();
1305 althrd_setname(althrd_current(), RECORD_THREAD_NAME
);
1307 while(!self
->killNow
)
1312 hr
= IAudioCaptureClient_GetNextPacketSize(self
->capture
, &avail
);
1314 ERR("Failed to get next packet size: 0x%08lx\n", hr
);
1321 hr
= IAudioCaptureClient_GetBuffer(self
->capture
,
1322 &rdata
, &numsamples
, &flags
, NULL
, NULL
1325 ERR("Failed to get capture buffer: 0x%08lx\n", hr
);
1328 ll_ringbuffer_data_t data
[2];
1329 size_t dstframes
= 0;
1331 if(self
->ChannelConv
)
1333 if(samplesmax
< numsamples
)
1335 size_t newmax
= RoundUp(numsamples
, 4096);
1336 ALfloat
*tmp
= al_calloc(DEF_ALIGN
, newmax
*2*sizeof(ALfloat
));
1339 samplesmax
= newmax
;
1341 ChannelConverterInput(self
->ChannelConv
, rdata
, samples
, numsamples
);
1342 rdata
= (BYTE
*)samples
;
1345 ll_ringbuffer_get_write_vector(self
->Ring
, data
);
1347 if(self
->SampleConv
)
1349 const ALvoid
*srcdata
= rdata
;
1350 ALsizei srcframes
= numsamples
;
1352 dstframes
= SampleConverterInput(self
->SampleConv
,
1353 &srcdata
, &srcframes
, data
[0].buf
, data
[0].len
1355 if(srcframes
> 0 && dstframes
== data
[0].len
&& data
[1].len
> 0)
1357 /* If some source samples remain, all of the first dest
1358 * block was filled, and there's space in the second
1359 * dest block, do another run for the second block.
1361 dstframes
+= SampleConverterInput(self
->SampleConv
,
1362 &srcdata
, &srcframes
, data
[1].buf
, data
[1].len
1368 size_t framesize
= FrameSizeFromDevFmt(device
->FmtChans
, device
->FmtType
,
1370 ALuint len1
= minu(data
[0].len
, numsamples
);
1371 ALuint len2
= minu(data
[1].len
, numsamples
-len1
);
1373 memcpy(data
[0].buf
, rdata
, len1
*framesize
);
1375 memcpy(data
[1].buf
, rdata
+len1
*framesize
, len2
*framesize
);
1376 dstframes
= len1
+ len2
;
1379 ll_ringbuffer_write_advance(self
->Ring
, dstframes
);
1381 hr
= IAudioCaptureClient_ReleaseBuffer(self
->capture
, numsamples
);
1382 if(FAILED(hr
)) ERR("Failed to release capture buffer: 0x%08lx\n", hr
);
1388 V0(device
->Backend
,lock
)();
1389 aluHandleDisconnect(device
);
1390 V0(device
->Backend
,unlock
)();
1394 res
= WaitForSingleObjectEx(self
->NotifyEvent
, 2000, FALSE
);
1395 if(res
!= WAIT_OBJECT_0
)
1396 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
1408 static ALCenum
ALCmmdevCapture_open(ALCmmdevCapture
*self
, const ALCchar
*deviceName
)
1412 self
->NotifyEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
1413 self
->MsgEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
1414 if(self
->NotifyEvent
== NULL
|| self
->MsgEvent
== NULL
)
1416 ERR("Failed to create message events: %lu\n", GetLastError());
1426 if(VECTOR_SIZE(CaptureDevices
) == 0)
1428 ThreadRequest req
= { self
->MsgEvent
, 0 };
1429 if(PostThreadMessage(ThreadID
, WM_USER_Enumerate
, (WPARAM
)&req
, CAPTURE_DEVICE_PROBE
))
1430 (void)WaitForResponse(&req
);
1434 #define MATCH_NAME(i) (alstr_cmp_cstr((i)->name, deviceName) == 0 || \
1435 alstr_cmp_cstr((i)->endpoint_guid, deviceName) == 0)
1436 VECTOR_FIND_IF(iter
, const DevMap
, CaptureDevices
, MATCH_NAME
);
1438 if(iter
== VECTOR_END(CaptureDevices
))
1441 if((len
=MultiByteToWideChar(CP_UTF8
, 0, deviceName
, -1, NULL
, 0)) > 0)
1443 WCHAR
*wname
= calloc(sizeof(WCHAR
), len
);
1444 MultiByteToWideChar(CP_UTF8
, 0, deviceName
, -1, wname
, len
);
1445 #define MATCH_NAME(i) (wcscmp((i)->devid, wname) == 0)
1446 VECTOR_FIND_IF(iter
, const DevMap
, CaptureDevices
, MATCH_NAME
);
1451 if(iter
== VECTOR_END(CaptureDevices
))
1452 WARN("Failed to find device name matching \"%s\"\n", deviceName
);
1455 ALCdevice
*device
= STATIC_CAST(ALCbackend
,self
)->mDevice
;
1456 self
->devid
= strdupW(iter
->devid
);
1457 alstr_copy(&device
->DeviceName
, iter
->name
);
1465 ThreadRequest req
= { self
->MsgEvent
, 0 };
1468 if(PostThreadMessage(ThreadID
, WM_USER_OpenDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1469 hr
= WaitForResponse(&req
);
1471 ERR("Failed to post thread message: %lu\n", GetLastError());
1476 if(self
->NotifyEvent
!= NULL
)
1477 CloseHandle(self
->NotifyEvent
);
1478 self
->NotifyEvent
= NULL
;
1479 if(self
->MsgEvent
!= NULL
)
1480 CloseHandle(self
->MsgEvent
);
1481 self
->MsgEvent
= NULL
;
1486 ERR("Device init failed: 0x%08lx\n", hr
);
1487 return ALC_INVALID_VALUE
;
1491 ThreadRequest req
= { self
->MsgEvent
, 0 };
1494 if(PostThreadMessage(ThreadID
, WM_USER_ResetDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1495 hr
= WaitForResponse(&req
);
1497 ERR("Failed to post thread message: %lu\n", GetLastError());
1501 ALCmmdevCapture_close(self
);
1502 if(hr
== E_OUTOFMEMORY
)
1503 return ALC_OUT_OF_MEMORY
;
1504 return ALC_INVALID_VALUE
;
1508 return ALC_NO_ERROR
;
1511 static HRESULT
ALCmmdevCapture_openProxy(ALCmmdevCapture
*self
)
1513 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
1517 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
1520 IMMDeviceEnumerator
*Enumerator
= ptr
;
1522 hr
= IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator
, eCapture
, eMultimedia
, &self
->mmdev
);
1524 hr
= IMMDeviceEnumerator_GetDevice(Enumerator
, self
->devid
, &self
->mmdev
);
1525 IMMDeviceEnumerator_Release(Enumerator
);
1529 hr
= IMMDevice_Activate(self
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
1533 if(alstr_empty(device
->DeviceName
))
1534 get_device_name_and_guid(self
->mmdev
, &device
->DeviceName
, NULL
);
1540 IMMDevice_Release(self
->mmdev
);
1548 static void ALCmmdevCapture_close(ALCmmdevCapture
*self
)
1550 ThreadRequest req
= { self
->MsgEvent
, 0 };
1552 if(PostThreadMessage(ThreadID
, WM_USER_CloseDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1553 (void)WaitForResponse(&req
);
1555 ll_ringbuffer_free(self
->Ring
);
1558 CloseHandle(self
->MsgEvent
);
1559 self
->MsgEvent
= NULL
;
1561 CloseHandle(self
->NotifyEvent
);
1562 self
->NotifyEvent
= NULL
;
1568 static void ALCmmdevCapture_closeProxy(ALCmmdevCapture
*self
)
1571 IAudioClient_Release(self
->client
);
1572 self
->client
= NULL
;
1575 IMMDevice_Release(self
->mmdev
);
1580 static HRESULT
ALCmmdevCapture_resetProxy(ALCmmdevCapture
*self
)
1582 ALCdevice
*device
= STATIC_CAST(ALCbackend
, self
)->mDevice
;
1583 WAVEFORMATEXTENSIBLE OutputType
;
1584 WAVEFORMATEX
*wfx
= NULL
;
1585 enum DevFmtType srcType
;
1586 REFERENCE_TIME buf_time
;
1592 IAudioClient_Release(self
->client
);
1593 self
->client
= NULL
;
1595 hr
= IMMDevice_Activate(self
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
1598 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
1603 buf_time
= ((REFERENCE_TIME
)device
->UpdateSize
*device
->NumUpdates
*10000000 +
1604 device
->Frequency
-1) / device
->Frequency
;
1605 // Make sure buffer is at least 100ms in size
1606 buf_time
= maxu64(buf_time
, U64(1000000));
1607 device
->UpdateSize
= (buf_time
*device
->Frequency
+ 10000000-1)/10000000 /
1610 OutputType
.Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
1611 switch(device
->FmtChans
)
1614 OutputType
.Format
.nChannels
= 1;
1615 OutputType
.dwChannelMask
= MONO
;
1618 OutputType
.Format
.nChannels
= 2;
1619 OutputType
.dwChannelMask
= STEREO
;
1622 OutputType
.Format
.nChannels
= 4;
1623 OutputType
.dwChannelMask
= QUAD
;
1626 OutputType
.Format
.nChannels
= 6;
1627 OutputType
.dwChannelMask
= X5DOT1
;
1630 OutputType
.Format
.nChannels
= 6;
1631 OutputType
.dwChannelMask
= X5DOT1REAR
;
1634 OutputType
.Format
.nChannels
= 7;
1635 OutputType
.dwChannelMask
= X6DOT1
;
1638 OutputType
.Format
.nChannels
= 8;
1639 OutputType
.dwChannelMask
= X7DOT1
;
1645 switch(device
->FmtType
)
1647 /* NOTE: Signedness doesn't matter, the converter will handle it. */
1650 OutputType
.Format
.wBitsPerSample
= 8;
1651 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1655 OutputType
.Format
.wBitsPerSample
= 16;
1656 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1660 OutputType
.Format
.wBitsPerSample
= 32;
1661 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1664 OutputType
.Format
.wBitsPerSample
= 32;
1665 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
1668 OutputType
.Samples
.wValidBitsPerSample
= OutputType
.Format
.wBitsPerSample
;
1669 OutputType
.Format
.nSamplesPerSec
= device
->Frequency
;
1671 OutputType
.Format
.nBlockAlign
= OutputType
.Format
.nChannels
*
1672 OutputType
.Format
.wBitsPerSample
/ 8;
1673 OutputType
.Format
.nAvgBytesPerSec
= OutputType
.Format
.nSamplesPerSec
*
1674 OutputType
.Format
.nBlockAlign
;
1675 OutputType
.Format
.cbSize
= sizeof(OutputType
) - sizeof(OutputType
.Format
);
1677 hr
= IAudioClient_IsFormatSupported(self
->client
,
1678 AUDCLNT_SHAREMODE_SHARED
, &OutputType
.Format
, &wfx
1682 ERR("Failed to check format support: 0x%08lx\n", hr
);
1686 DestroySampleConverter(&self
->SampleConv
);
1687 DestroyChannelConverter(&self
->ChannelConv
);
1691 if(!(wfx
->nChannels
== OutputType
.Format
.nChannels
||
1692 (wfx
->nChannels
== 1 && OutputType
.Format
.nChannels
== 2) ||
1693 (wfx
->nChannels
== 2 && OutputType
.Format
.nChannels
== 1)))
1695 ERR("Failed to get matching format, wanted: %s %s %uhz, got: %d channel%s %d-bit %luhz\n",
1696 DevFmtChannelsString(device
->FmtChans
), DevFmtTypeString(device
->FmtType
),
1697 device
->Frequency
, wfx
->nChannels
, (wfx
->nChannels
==1)?"":"s", wfx
->wBitsPerSample
,
1698 wfx
->nSamplesPerSec
);
1703 if(!MakeExtensible(&OutputType
, wfx
))
1712 if(IsEqualGUID(&OutputType
.SubFormat
, &KSDATAFORMAT_SUBTYPE_PCM
))
1714 if(OutputType
.Format
.wBitsPerSample
== 8)
1715 srcType
= DevFmtUByte
;
1716 else if(OutputType
.Format
.wBitsPerSample
== 16)
1717 srcType
= DevFmtShort
;
1718 else if(OutputType
.Format
.wBitsPerSample
== 32)
1719 srcType
= DevFmtInt
;
1722 ERR("Unhandled integer bit depth: %d\n", OutputType
.Format
.wBitsPerSample
);
1726 else if(IsEqualGUID(&OutputType
.SubFormat
, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
1728 if(OutputType
.Format
.wBitsPerSample
== 32)
1729 srcType
= DevFmtFloat
;
1732 ERR("Unhandled float bit depth: %d\n", OutputType
.Format
.wBitsPerSample
);
1738 ERR("Unhandled format sub-type\n");
1742 if(device
->FmtChans
== DevFmtMono
&& OutputType
.Format
.nChannels
== 2)
1744 self
->ChannelConv
= CreateChannelConverter(srcType
, DevFmtStereo
,
1746 if(!self
->ChannelConv
)
1748 ERR("Failed to create %s stereo-to-mono converter\n", DevFmtTypeString(srcType
));
1751 TRACE("Created %s stereo-to-mono converter\n", DevFmtTypeString(srcType
));
1752 /* The channel converter always outputs float, so change the input type
1753 * for the resampler/type-converter.
1755 srcType
= DevFmtFloat
;
1757 else if(device
->FmtChans
== DevFmtStereo
&& OutputType
.Format
.nChannels
== 1)
1759 self
->ChannelConv
= CreateChannelConverter(srcType
, DevFmtMono
,
1761 if(!self
->ChannelConv
)
1763 ERR("Failed to create %s mono-to-stereo converter\n", DevFmtTypeString(srcType
));
1766 TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType
));
1767 srcType
= DevFmtFloat
;
1770 if(device
->Frequency
!= OutputType
.Format
.nSamplesPerSec
|| device
->FmtType
!= srcType
)
1772 self
->SampleConv
= CreateSampleConverter(
1773 srcType
, device
->FmtType
, ChannelsFromDevFmt(device
->FmtChans
, device
->AmbiOrder
),
1774 OutputType
.Format
.nSamplesPerSec
, device
->Frequency
1776 if(!self
->SampleConv
)
1778 ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1779 DevFmtChannelsString(device
->FmtChans
), DevFmtTypeString(device
->FmtType
),
1780 device
->Frequency
, DevFmtTypeString(srcType
), OutputType
.Format
.nSamplesPerSec
);
1783 TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1784 DevFmtChannelsString(device
->FmtChans
), DevFmtTypeString(device
->FmtType
),
1785 device
->Frequency
, DevFmtTypeString(srcType
), OutputType
.Format
.nSamplesPerSec
);
1788 hr
= IAudioClient_Initialize(self
->client
,
1789 AUDCLNT_SHAREMODE_SHARED
, AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
1790 buf_time
, 0, &OutputType
.Format
, NULL
1794 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
1798 hr
= IAudioClient_GetBufferSize(self
->client
, &buffer_len
);
1801 ERR("Failed to get buffer size: 0x%08lx\n", hr
);
1805 buffer_len
= maxu(device
->UpdateSize
*device
->NumUpdates
+ 1, buffer_len
);
1806 ll_ringbuffer_free(self
->Ring
);
1807 self
->Ring
= ll_ringbuffer_create(buffer_len
,
1808 FrameSizeFromDevFmt(device
->FmtChans
, device
->FmtType
, device
->AmbiOrder
)
1812 ERR("Failed to allocate capture ring buffer\n");
1813 return E_OUTOFMEMORY
;
1816 hr
= IAudioClient_SetEventHandle(self
->client
, self
->NotifyEvent
);
1819 ERR("Failed to set event handle: 0x%08lx\n", hr
);
1827 static ALCboolean
ALCmmdevCapture_start(ALCmmdevCapture
*self
)
1829 ThreadRequest req
= { self
->MsgEvent
, 0 };
1830 HRESULT hr
= E_FAIL
;
1832 if(PostThreadMessage(ThreadID
, WM_USER_StartDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1833 hr
= WaitForResponse(&req
);
1835 return SUCCEEDED(hr
) ? ALC_TRUE
: ALC_FALSE
;
1838 static HRESULT
ALCmmdevCapture_startProxy(ALCmmdevCapture
*self
)
1843 ResetEvent(self
->NotifyEvent
);
1844 hr
= IAudioClient_Start(self
->client
);
1847 ERR("Failed to start audio client: 0x%08lx\n", hr
);
1851 hr
= IAudioClient_GetService(self
->client
, &IID_IAudioCaptureClient
, &ptr
);
1854 self
->capture
= ptr
;
1856 if(althrd_create(&self
->thread
, ALCmmdevCapture_recordProc
, self
) != althrd_success
)
1858 ERR("Failed to start thread\n");
1859 IAudioCaptureClient_Release(self
->capture
);
1860 self
->capture
= NULL
;
1867 IAudioClient_Stop(self
->client
);
1868 IAudioClient_Reset(self
->client
);
1875 static void ALCmmdevCapture_stop(ALCmmdevCapture
*self
)
1877 ThreadRequest req
= { self
->MsgEvent
, 0 };
1878 if(PostThreadMessage(ThreadID
, WM_USER_StopDevice
, (WPARAM
)&req
, (LPARAM
)STATIC_CAST(ALCmmdevProxy
, self
)))
1879 (void)WaitForResponse(&req
);
1882 static void ALCmmdevCapture_stopProxy(ALCmmdevCapture
*self
)
1890 althrd_join(self
->thread
, &res
);
1892 IAudioCaptureClient_Release(self
->capture
);
1893 self
->capture
= NULL
;
1894 IAudioClient_Stop(self
->client
);
1895 IAudioClient_Reset(self
->client
);
1899 ALuint
ALCmmdevCapture_availableSamples(ALCmmdevCapture
*self
)
1901 return (ALuint
)ll_ringbuffer_read_space(self
->Ring
);
1904 ALCenum
ALCmmdevCapture_captureSamples(ALCmmdevCapture
*self
, ALCvoid
*buffer
, ALCuint samples
)
1906 if(ALCmmdevCapture_availableSamples(self
) < samples
)
1907 return ALC_INVALID_VALUE
;
1908 ll_ringbuffer_read(self
->Ring
, buffer
, samples
);
1909 return ALC_NO_ERROR
;
1913 static inline void AppendAllDevicesList2(const DevMap
*entry
)
1914 { AppendAllDevicesList(alstr_get_cstr(entry
->name
)); }
1915 static inline void AppendCaptureDeviceList2(const DevMap
*entry
)
1916 { AppendCaptureDeviceList(alstr_get_cstr(entry
->name
)); }
1918 typedef struct ALCmmdevBackendFactory
{
1919 DERIVE_FROM_TYPE(ALCbackendFactory
);
1920 } ALCmmdevBackendFactory
;
1921 #define ALCMMDEVBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCmmdevBackendFactory, ALCbackendFactory) } }
1923 static ALCboolean
ALCmmdevBackendFactory_init(ALCmmdevBackendFactory
*self
);
1924 static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory
*self
);
1925 static ALCboolean
ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory
*self
, ALCbackend_Type type
);
1926 static void ALCmmdevBackendFactory_probe(ALCmmdevBackendFactory
*self
, enum DevProbe type
);
1927 static ALCbackend
* ALCmmdevBackendFactory_createBackend(ALCmmdevBackendFactory
*self
, ALCdevice
*device
, ALCbackend_Type type
);
1929 DEFINE_ALCBACKENDFACTORY_VTABLE(ALCmmdevBackendFactory
);
1932 static BOOL
MMDevApiLoad(void)
1934 static HRESULT InitResult
;
1938 InitResult
= E_FAIL
;
1940 req
.FinishedEvt
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
1941 if(req
.FinishedEvt
== NULL
)
1942 ERR("Failed to create event: %lu\n", GetLastError());
1945 ThreadHdl
= CreateThread(NULL
, 0, ALCmmdevProxy_messageHandler
, &req
, 0, &ThreadID
);
1946 if(ThreadHdl
!= NULL
)
1947 InitResult
= WaitForResponse(&req
);
1948 CloseHandle(req
.FinishedEvt
);
1951 return SUCCEEDED(InitResult
);
1954 static ALCboolean
ALCmmdevBackendFactory_init(ALCmmdevBackendFactory
* UNUSED(self
))
1956 VECTOR_INIT(PlaybackDevices
);
1957 VECTOR_INIT(CaptureDevices
);
1964 static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory
* UNUSED(self
))
1966 clear_devlist(&PlaybackDevices
);
1967 VECTOR_DEINIT(PlaybackDevices
);
1969 clear_devlist(&CaptureDevices
);
1970 VECTOR_DEINIT(CaptureDevices
);
1974 TRACE("Sending WM_QUIT to Thread %04lx\n", ThreadID
);
1975 PostThreadMessage(ThreadID
, WM_QUIT
, 0, 0);
1976 CloseHandle(ThreadHdl
);
1981 static ALCboolean
ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory
* UNUSED(self
), ALCbackend_Type type
)
1983 /* TODO: Disable capture with mmdevapi for now, since it doesn't do any
1984 * rechanneling or resampling; if the device is configured for 48000hz
1985 * stereo input, for example, and the app asks for 22050hz mono,
1986 * initialization will fail.
1988 if(type
== ALCbackend_Playback
|| type
== ALCbackend_Capture
)
1993 static void ALCmmdevBackendFactory_probe(ALCmmdevBackendFactory
* UNUSED(self
), enum DevProbe type
)
1995 ThreadRequest req
= { NULL
, 0 };
1997 req
.FinishedEvt
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
1998 if(req
.FinishedEvt
== NULL
)
1999 ERR("Failed to create event: %lu\n", GetLastError());
2002 HRESULT hr
= E_FAIL
;
2003 if(PostThreadMessage(ThreadID
, WM_USER_Enumerate
, (WPARAM
)&req
, type
))
2004 hr
= WaitForResponse(&req
);
2005 if(SUCCEEDED(hr
)) switch(type
)
2007 case ALL_DEVICE_PROBE
:
2008 VECTOR_FOR_EACH(const DevMap
, PlaybackDevices
, AppendAllDevicesList2
);
2011 case CAPTURE_DEVICE_PROBE
:
2012 VECTOR_FOR_EACH(const DevMap
, CaptureDevices
, AppendCaptureDeviceList2
);
2015 CloseHandle(req
.FinishedEvt
);
2016 req
.FinishedEvt
= NULL
;
2020 static ALCbackend
* ALCmmdevBackendFactory_createBackend(ALCmmdevBackendFactory
* UNUSED(self
), ALCdevice
*device
, ALCbackend_Type type
)
2022 if(type
== ALCbackend_Playback
)
2024 ALCmmdevPlayback
*backend
;
2025 NEW_OBJ(backend
, ALCmmdevPlayback
)(device
);
2026 if(!backend
) return NULL
;
2027 return STATIC_CAST(ALCbackend
, backend
);
2029 if(type
== ALCbackend_Capture
)
2031 ALCmmdevCapture
*backend
;
2032 NEW_OBJ(backend
, ALCmmdevCapture
)(device
);
2033 if(!backend
) return NULL
;
2034 return STATIC_CAST(ALCbackend
, backend
);
2041 ALCbackendFactory
*ALCmmdevBackendFactory_getFactory(void)
2043 static ALCmmdevBackendFactory factory
= ALCMMDEVBACKENDFACTORY_INITIALIZER
;
2044 return STATIC_CAST(ALCbackendFactory
, &factory
);