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., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
28 #include <mmdeviceapi.h>
29 #include <audioclient.h>
32 #ifndef _WAVEFORMATEXTENSIBLE_
42 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM
, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
43 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
45 #define MONO SPEAKER_FRONT_CENTER
46 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
47 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
48 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
49 #define X5DOT1SIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
50 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
51 #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 static const ALCchar mmDevice
[] = "WASAPI Default";
69 static HANDLE ThreadHdl
;
70 static DWORD ThreadID
;
77 #define WM_USER_OpenDevice (WM_USER+0)
78 #define WM_USER_ResetDevice (WM_USER+1)
79 #define WM_USER_StopDevice (WM_USER+2)
80 #define WM_USER_CloseDevice (WM_USER+3)
82 static HRESULT
WaitForResponse(ThreadRequest
*req
)
84 if(WaitForSingleObject(req
->FinishedEvt
, INFINITE
) == WAIT_OBJECT_0
)
86 ERR("Message response error: %lu\n", GetLastError());
91 static ALuint
MMDevApiProc(ALvoid
*ptr
)
93 ALCdevice
*device
= ptr
;
94 MMDevApiData
*data
= device
->ExtraData
;
96 IAudioRenderClient
*iface
;
103 hr
= CoInitialize(NULL
);
106 ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr
);
107 aluHandleDisconnect(device
);
111 hr
= IAudioClient_GetService(data
->client
, &IID_IAudioRenderClient
, &render
.ptr
);
114 ERR("Failed to get AudioRenderClient service: 0x%08lx\n", hr
);
115 aluHandleDisconnect(device
);
121 while(!data
->killNow
)
123 hr
= IAudioClient_GetCurrentPadding(data
->client
, &written
);
126 ERR("Failed to get padding: 0x%08lx\n", hr
);
127 aluHandleDisconnect(device
);
131 len
= device
->UpdateSize
*device
->NumUpdates
- written
;
132 if(len
< device
->UpdateSize
)
135 res
= WaitForSingleObjectEx(data
->hNotifyEvent
, 2000, FALSE
);
136 if(res
!= WAIT_OBJECT_0
)
137 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
140 len
-= len
%device
->UpdateSize
;
142 hr
= IAudioRenderClient_GetBuffer(render
.iface
, len
, &buffer
);
145 aluMixData(device
, buffer
, len
);
146 hr
= IAudioRenderClient_ReleaseBuffer(render
.iface
, len
, 0);
150 ERR("Failed to buffer data: 0x%08lx\n", hr
);
151 aluHandleDisconnect(device
);
156 IAudioRenderClient_Release(render
.iface
);
163 static ALCboolean
MakeExtensible(WAVEFORMATEXTENSIBLE
*out
, const WAVEFORMATEX
*in
)
165 memset(out
, 0, sizeof(*out
));
166 if(in
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
)
167 *out
= *(WAVEFORMATEXTENSIBLE
*)in
;
168 else if(in
->wFormatTag
== WAVE_FORMAT_PCM
)
171 out
->Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
172 out
->Format
.cbSize
= sizeof(*out
) - sizeof(*in
);
173 if(out
->Format
.nChannels
== 1)
174 out
->dwChannelMask
= MONO
;
175 else if(out
->Format
.nChannels
== 2)
176 out
->dwChannelMask
= STEREO
;
178 ERR("Unhandled PCM channel count: %d\n", out
->Format
.nChannels
);
179 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
181 else if(in
->wFormatTag
== WAVE_FORMAT_IEEE_FLOAT
)
184 out
->Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
185 out
->Format
.cbSize
= sizeof(*out
) - sizeof(*in
);
186 if(out
->Format
.nChannels
== 1)
187 out
->dwChannelMask
= MONO
;
188 else if(out
->Format
.nChannels
== 2)
189 out
->dwChannelMask
= STEREO
;
191 ERR("Unhandled IEEE float channel count: %d\n", out
->Format
.nChannels
);
192 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
196 ERR("Unhandled format tag: 0x%04x\n", in
->wFormatTag
);
202 static HRESULT
DoReset(ALCdevice
*device
)
204 MMDevApiData
*data
= device
->ExtraData
;
205 WAVEFORMATEXTENSIBLE OutputType
;
206 WAVEFORMATEX
*wfx
= NULL
;
207 REFERENCE_TIME min_per
;
208 UINT32 buffer_len
, min_len
;
211 hr
= IAudioClient_GetMixFormat(data
->client
, &wfx
);
214 ERR("Failed to get mix format: 0x%08lx\n", hr
);
218 if(!MakeExtensible(&OutputType
, wfx
))
226 if(!(device
->Flags
&DEVICE_FREQUENCY_REQUEST
))
227 device
->Frequency
= OutputType
.Format
.nSamplesPerSec
;
228 if(!(device
->Flags
&DEVICE_CHANNELS_REQUEST
))
230 if(OutputType
.Format
.nChannels
== 1 && OutputType
.dwChannelMask
== MONO
)
231 device
->FmtChans
= DevFmtMono
;
232 else if(OutputType
.Format
.nChannels
== 2 && OutputType
.dwChannelMask
== STEREO
)
233 device
->FmtChans
= DevFmtStereo
;
234 else if(OutputType
.Format
.nChannels
== 4 && OutputType
.dwChannelMask
== QUAD
)
235 device
->FmtChans
= DevFmtQuad
;
236 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1
)
237 device
->FmtChans
= DevFmtX51
;
238 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1SIDE
)
239 device
->FmtChans
= DevFmtX51Side
;
240 else if(OutputType
.Format
.nChannels
== 7 && OutputType
.dwChannelMask
== X6DOT1
)
241 device
->FmtChans
= DevFmtX61
;
242 else if(OutputType
.Format
.nChannels
== 8 && OutputType
.dwChannelMask
== X7DOT1
)
243 device
->FmtChans
= DevFmtX71
;
245 ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType
.Format
.nChannels
, OutputType
.dwChannelMask
);
248 switch(device
->FmtChans
)
251 OutputType
.Format
.nChannels
= 1;
252 OutputType
.dwChannelMask
= MONO
;
255 OutputType
.Format
.nChannels
= 2;
256 OutputType
.dwChannelMask
= STEREO
;
259 OutputType
.Format
.nChannels
= 4;
260 OutputType
.dwChannelMask
= QUAD
;
263 OutputType
.Format
.nChannels
= 6;
264 OutputType
.dwChannelMask
= X5DOT1
;
267 OutputType
.Format
.nChannels
= 6;
268 OutputType
.dwChannelMask
= X5DOT1SIDE
;
271 OutputType
.Format
.nChannels
= 7;
272 OutputType
.dwChannelMask
= X6DOT1
;
275 OutputType
.Format
.nChannels
= 8;
276 OutputType
.dwChannelMask
= X7DOT1
;
279 switch(device
->FmtType
)
282 device
->FmtType
= DevFmtUByte
;
285 OutputType
.Format
.wBitsPerSample
= 8;
286 OutputType
.Samples
.wValidBitsPerSample
= 8;
287 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
290 device
->FmtType
= DevFmtShort
;
293 OutputType
.Format
.wBitsPerSample
= 16;
294 OutputType
.Samples
.wValidBitsPerSample
= 16;
295 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
298 OutputType
.Format
.wBitsPerSample
= 32;
299 OutputType
.Samples
.wValidBitsPerSample
= 32;
300 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
303 OutputType
.Format
.nSamplesPerSec
= device
->Frequency
;
305 OutputType
.Format
.nBlockAlign
= OutputType
.Format
.nChannels
*
306 OutputType
.Format
.wBitsPerSample
/ 8;
307 OutputType
.Format
.nAvgBytesPerSec
= OutputType
.Format
.nSamplesPerSec
*
308 OutputType
.Format
.nBlockAlign
;
310 hr
= IAudioClient_IsFormatSupported(data
->client
, AUDCLNT_SHAREMODE_SHARED
, &OutputType
.Format
, &wfx
);
313 ERR("Failed to check format support: 0x%08lx\n", hr
);
314 hr
= IAudioClient_GetMixFormat(data
->client
, &wfx
);
318 ERR("Failed to find a supported format: 0x%08lx\n", hr
);
324 if(!MakeExtensible(&OutputType
, wfx
))
332 device
->Frequency
= OutputType
.Format
.nSamplesPerSec
;
333 if(OutputType
.Format
.nChannels
== 1 && OutputType
.dwChannelMask
== MONO
)
334 device
->FmtChans
= DevFmtMono
;
335 else if(OutputType
.Format
.nChannels
== 2 && OutputType
.dwChannelMask
== STEREO
)
336 device
->FmtChans
= DevFmtStereo
;
337 else if(OutputType
.Format
.nChannels
== 4 && OutputType
.dwChannelMask
== QUAD
)
338 device
->FmtChans
= DevFmtQuad
;
339 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1
)
340 device
->FmtChans
= DevFmtX51
;
341 else if(OutputType
.Format
.nChannels
== 6 && OutputType
.dwChannelMask
== X5DOT1SIDE
)
342 device
->FmtChans
= DevFmtX51Side
;
343 else if(OutputType
.Format
.nChannels
== 7 && OutputType
.dwChannelMask
== X6DOT1
)
344 device
->FmtChans
= DevFmtX61
;
345 else if(OutputType
.Format
.nChannels
== 8 && OutputType
.dwChannelMask
== X7DOT1
)
346 device
->FmtChans
= DevFmtX71
;
349 ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType
.Format
.nChannels
, OutputType
.dwChannelMask
);
350 device
->FmtChans
= DevFmtStereo
;
351 OutputType
.Format
.nChannels
= 2;
352 OutputType
.dwChannelMask
= STEREO
;
355 if(IsEqualGUID(&OutputType
.SubFormat
, &KSDATAFORMAT_SUBTYPE_PCM
))
357 if(OutputType
.Format
.wBitsPerSample
== 8)
358 device
->FmtType
= DevFmtUByte
;
359 else if(OutputType
.Format
.wBitsPerSample
== 16)
360 device
->FmtType
= DevFmtShort
;
363 device
->FmtType
= DevFmtShort
;
364 OutputType
.Format
.wBitsPerSample
= 16;
367 else if(IsEqualGUID(&OutputType
.SubFormat
, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
369 device
->FmtType
= DevFmtFloat
;
370 OutputType
.Format
.wBitsPerSample
= 32;
374 ERR("Unhandled format sub-type\n");
375 device
->FmtType
= DevFmtShort
;
376 OutputType
.Format
.wBitsPerSample
= 16;
377 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
379 OutputType
.Samples
.wValidBitsPerSample
= OutputType
.Format
.wBitsPerSample
;
382 SetDefaultWFXChannelOrder(device
);
384 hr
= IAudioClient_GetDevicePeriod(data
->client
, &min_per
, NULL
);
387 min_len
= (min_per
*device
->Frequency
+ 10000000-1) / 10000000;
388 if(min_len
< device
->UpdateSize
)
389 min_len
*= (device
->UpdateSize
+ min_len
/2)/min_len
;
391 device
->NumUpdates
= (device
->NumUpdates
*device
->UpdateSize
+ min_len
/2) /
393 device
->NumUpdates
= maxu(device
->NumUpdates
, 2);
394 device
->UpdateSize
= min_len
;
396 hr
= IAudioClient_Initialize(data
->client
, AUDCLNT_SHAREMODE_SHARED
,
397 AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
398 ((REFERENCE_TIME
)device
->UpdateSize
*
399 device
->NumUpdates
*10000000 +
400 device
->Frequency
-1) / device
->Frequency
,
401 0, &OutputType
.Format
, NULL
);
405 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
409 hr
= IAudioClient_GetBufferSize(data
->client
, &buffer_len
);
412 ERR("Failed to get audio buffer info: 0x%08lx\n", hr
);
416 device
->NumUpdates
= buffer_len
/ device
->UpdateSize
;
417 if(device
->NumUpdates
<= 1)
419 device
->NumUpdates
= 1;
420 ERR("Audio client returned buffer_len < period*2; expect break up\n");
423 ResetEvent(data
->hNotifyEvent
);
424 hr
= IAudioClient_SetEventHandle(data
->client
, data
->hNotifyEvent
);
426 hr
= IAudioClient_Start(data
->client
);
429 ERR("Failed to start audio client: 0x%08lx\n", hr
);
433 data
->thread
= StartThread(MMDevApiProc
, device
);
436 IAudioClient_Stop(data
->client
);
437 ERR("Failed to start thread\n");
445 static DWORD CALLBACK
MMDevApiMsgProc(void *ptr
)
447 ThreadRequest
*req
= ptr
;
448 IMMDeviceEnumerator
*Enumerator
;
449 ALuint deviceCount
= 0;
455 TRACE("Starting message thread\n");
457 hr
= CoInitialize(NULL
);
460 WARN("Failed to initialize COM: 0x%08lx\n", hr
);
462 SetEvent(req
->FinishedEvt
);
466 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
469 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr
);
472 SetEvent(req
->FinishedEvt
);
476 IMMDeviceEnumerator_Release(Enumerator
);
482 SetEvent(req
->FinishedEvt
);
484 TRACE("Starting message loop\n");
485 while(GetMessage(&msg
, NULL
, 0, 0))
487 TRACE("Got message %u\n", msg
.message
);
490 case WM_USER_OpenDevice
:
491 req
= (ThreadRequest
*)msg
.wParam
;
492 device
= (ALCdevice
*)msg
.lParam
;
493 data
= device
->ExtraData
;
496 if(++deviceCount
== 1)
497 hr
= CoInitialize(NULL
);
499 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, &ptr
);
503 hr
= IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator
, eRender
, eMultimedia
, &data
->mmdev
);
504 IMMDeviceEnumerator_Release(Enumerator
);
508 hr
= IMMDevice_Activate(data
->mmdev
, &IID_IAudioClient
, CLSCTX_INPROC_SERVER
, NULL
, &ptr
);
515 IMMDevice_Release(data
->mmdev
);
520 SetEvent(req
->FinishedEvt
);
523 case WM_USER_ResetDevice
:
524 req
= (ThreadRequest
*)msg
.wParam
;
525 device
= (ALCdevice
*)msg
.lParam
;
527 req
->result
= DoReset(device
);
528 SetEvent(req
->FinishedEvt
);
531 case WM_USER_StopDevice
:
532 req
= (ThreadRequest
*)msg
.wParam
;
533 device
= (ALCdevice
*)msg
.lParam
;
534 data
= device
->ExtraData
;
539 StopThread(data
->thread
);
544 IAudioClient_Stop(data
->client
);
548 SetEvent(req
->FinishedEvt
);
551 case WM_USER_CloseDevice
:
552 req
= (ThreadRequest
*)msg
.wParam
;
553 device
= (ALCdevice
*)msg
.lParam
;
554 data
= device
->ExtraData
;
556 IAudioClient_Release(data
->client
);
559 IMMDevice_Release(data
->mmdev
);
562 if(--deviceCount
== 0)
566 SetEvent(req
->FinishedEvt
);
570 ERR("Unexpected message: %u\n", msg
.message
);
574 TRACE("Message loop finished\n");
580 static BOOL
MMDevApiLoad(void)
582 static HRESULT InitResult
;
588 req
.FinishedEvt
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
589 if(req
.FinishedEvt
== NULL
)
590 ERR("Failed to create event: %lu\n", GetLastError());
593 ThreadHdl
= CreateThread(NULL
, 0, MMDevApiMsgProc
, &req
, 0, &ThreadID
);
594 if(ThreadHdl
!= NULL
)
595 InitResult
= WaitForResponse(&req
);
596 CloseHandle(req
.FinishedEvt
);
599 return SUCCEEDED(InitResult
);
603 static ALCenum
MMDevApiOpenPlayback(ALCdevice
*device
, const ALCchar
*deviceName
)
605 MMDevApiData
*data
= NULL
;
609 deviceName
= mmDevice
;
610 else if(strcmp(deviceName
, mmDevice
) != 0)
611 return ALC_INVALID_VALUE
;
613 //Initialise requested device
614 data
= calloc(1, sizeof(MMDevApiData
));
616 return ALC_OUT_OF_MEMORY
;
617 device
->ExtraData
= data
;
620 data
->hNotifyEvent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
621 data
->MsgEvent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
622 if(data
->hNotifyEvent
== NULL
|| data
->MsgEvent
== NULL
)
627 ThreadRequest req
= { data
->MsgEvent
, 0 };
630 if(PostThreadMessage(ThreadID
, WM_USER_OpenDevice
, (WPARAM
)&req
, (LPARAM
)device
))
631 hr
= WaitForResponse(&req
);
636 if(data
->hNotifyEvent
!= NULL
)
637 CloseHandle(data
->hNotifyEvent
);
638 data
->hNotifyEvent
= NULL
;
639 if(data
->MsgEvent
!= NULL
)
640 CloseHandle(data
->MsgEvent
);
641 data
->MsgEvent
= NULL
;
644 device
->ExtraData
= NULL
;
646 ERR("Device init failed: 0x%08lx\n", hr
);
647 return ALC_INVALID_VALUE
;
650 device
->szDeviceName
= strdup(deviceName
);
654 static void MMDevApiClosePlayback(ALCdevice
*device
)
656 MMDevApiData
*data
= device
->ExtraData
;
657 ThreadRequest req
= { data
->MsgEvent
, 0 };
659 if(PostThreadMessage(ThreadID
, WM_USER_CloseDevice
, (WPARAM
)&req
, (LPARAM
)device
))
660 (void)WaitForResponse(&req
);
662 CloseHandle(data
->MsgEvent
);
663 data
->MsgEvent
= NULL
;
665 CloseHandle(data
->hNotifyEvent
);
666 data
->hNotifyEvent
= NULL
;
669 device
->ExtraData
= NULL
;
672 static ALCboolean
MMDevApiResetPlayback(ALCdevice
*device
)
674 MMDevApiData
*data
= device
->ExtraData
;
675 ThreadRequest req
= { data
->MsgEvent
, 0 };
678 if(PostThreadMessage(ThreadID
, WM_USER_ResetDevice
, (WPARAM
)&req
, (LPARAM
)device
))
679 hr
= WaitForResponse(&req
);
681 return SUCCEEDED(hr
) ? ALC_TRUE
: ALC_FALSE
;
684 static void MMDevApiStopPlayback(ALCdevice
*device
)
686 MMDevApiData
*data
= device
->ExtraData
;
687 ThreadRequest req
= { data
->MsgEvent
, 0 };
689 if(PostThreadMessage(ThreadID
, WM_USER_StopDevice
, (WPARAM
)&req
, (LPARAM
)device
))
690 (void)WaitForResponse(&req
);
694 static const BackendFuncs MMDevApiFuncs
= {
695 MMDevApiOpenPlayback
,
696 MMDevApiClosePlayback
,
697 MMDevApiResetPlayback
,
698 MMDevApiStopPlayback
,
708 ALCboolean
alcMMDevApiInit(BackendFuncs
*FuncList
)
712 *FuncList
= MMDevApiFuncs
;
716 void alcMMDevApiDeinit(void)
720 TRACE("Sending WM_QUIT to Thread %04lx\n", ThreadID
);
721 PostThreadMessage(ThreadID
, WM_QUIT
, 0, 0);
722 CloseHandle(ThreadHdl
);
727 void alcMMDevApiProbe(enum DevProbe type
)
732 AppendDeviceList(mmDevice
);
734 case ALL_DEVICE_PROBE
:
735 AppendAllDeviceList(mmDevice
);
737 case CAPTURE_DEVICE_PROBE
: