c689f506f27d4346715bdf65b1c9ec1440ee702e
[platform/upstream/gstreamer.git] / sys / wasapi / gstwasapiutil.c
1 /*
2  * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
3  * Copyright (C) 2018 Centricular Ltd.
4  *   Author: Nirbheek Chauhan <nirbheek@centricular.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24
25 #include "gstwasapiutil.h"
26 #include "gstwasapidevice.h"
27
28 GST_DEBUG_CATEGORY_EXTERN (gst_wasapi_debug);
29 #define GST_CAT_DEFAULT gst_wasapi_debug
30
31 /* This was only added to MinGW in ~2015 and our Cerbero toolchain is too old */
32 #if defined(_MSC_VER)
33 #include <functiondiscoverykeys_devpkey.h>
34 #elif !defined(PKEY_Device_FriendlyName)
35 #include <propkey.h>
36 DEFINE_PROPERTYKEY (PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,
37     0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
38 DEFINE_PROPERTYKEY (PKEY_AudioEngine_DeviceFormat, 0xf19f064d, 0x82c, 0x4e27,
39     0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, 0);
40 #endif
41
42 /* __uuidof is only available in C++, so we hard-code the GUID values for all
43  * these. This is ok because these are ABI. */
44 const CLSID CLSID_MMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c,
45   {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e}
46 };
47
48 const IID IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35,
49   {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6}
50 };
51
52 const IID IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,
53   {0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5}
54 };
55
56 const IID IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32,
57   {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2}
58 };
59
60 const IID IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4,
61   {0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42}
62 };
63
64 const IID IID_IAudioClock = { 0xcd63314f, 0x3fba, 0x4a1b,
65   {0x81, 0x2c, 0xef, 0x96, 0x35, 0x87, 0x28, 0xe7}
66 };
67
68 const IID IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0,
69   {0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17}
70 };
71
72 const IID IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483,
73   {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2}
74 };
75
76 /* *INDENT-OFF* */
77 static struct
78 {
79   guint64 wasapi_pos;
80   GstAudioChannelPosition gst_pos;
81 } wasapi_to_gst_pos[] = {
82   {SPEAKER_FRONT_LEFT, GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT},
83   {SPEAKER_FRONT_RIGHT, GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT},
84   {SPEAKER_FRONT_CENTER, GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER},
85   {SPEAKER_LOW_FREQUENCY, GST_AUDIO_CHANNEL_POSITION_LFE1},
86   {SPEAKER_BACK_LEFT, GST_AUDIO_CHANNEL_POSITION_REAR_LEFT},
87   {SPEAKER_BACK_RIGHT, GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT},
88   {SPEAKER_FRONT_LEFT_OF_CENTER,
89       GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER},
90   {SPEAKER_FRONT_RIGHT_OF_CENTER,
91       GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER},
92   {SPEAKER_BACK_CENTER, GST_AUDIO_CHANNEL_POSITION_REAR_CENTER},
93   /* Enum values diverge from this point onwards */
94   {SPEAKER_SIDE_LEFT, GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT},
95   {SPEAKER_SIDE_RIGHT, GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT},
96   {SPEAKER_TOP_CENTER, GST_AUDIO_CHANNEL_POSITION_TOP_CENTER},
97   {SPEAKER_TOP_FRONT_LEFT, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT},
98   {SPEAKER_TOP_FRONT_CENTER, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER},
99   {SPEAKER_TOP_FRONT_RIGHT, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT},
100   {SPEAKER_TOP_BACK_LEFT, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT},
101   {SPEAKER_TOP_BACK_CENTER, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER},
102   {SPEAKER_TOP_BACK_RIGHT, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT}
103 };
104 /* *INDENT-ON* */
105
106 static int windows_major_version = 0;
107
108 static struct
109 {
110   HMODULE dll;
111   gboolean tried_loading;
112
113     HANDLE (WINAPI * AvSetMmThreadCharacteristics) (LPCSTR, LPDWORD);
114     BOOL (WINAPI * AvRevertMmThreadCharacteristics) (HANDLE);
115 } gst_wasapi_avrt_tbl = {
116 0};
117
118 gboolean
119 gst_wasapi_util_have_audioclient3 (void)
120 {
121   if (windows_major_version > 0)
122     return windows_major_version == 10;
123
124   if (g_getenv ("GST_WASAPI_DISABLE_AUDIOCLIENT3") != NULL) {
125     windows_major_version = 6;
126     return FALSE;
127   }
128
129   /* https://msdn.microsoft.com/en-us/library/windows/desktop/ms724834(v=vs.85).aspx */
130   windows_major_version = 6;
131   if (g_win32_check_windows_version (10, 0, 0, G_WIN32_OS_ANY))
132     windows_major_version = 10;
133
134   return windows_major_version == 10;
135 }
136
137 GType
138 gst_wasapi_device_role_get_type (void)
139 {
140   static const GEnumValue values[] = {
141     {GST_WASAPI_DEVICE_ROLE_CONSOLE,
142         "Games, system notifications, voice commands", "console"},
143     {GST_WASAPI_DEVICE_ROLE_MULTIMEDIA, "Music, movies, recorded media",
144         "multimedia"},
145     {GST_WASAPI_DEVICE_ROLE_COMMS, "Voice communications", "comms"},
146     {0, NULL, NULL}
147   };
148   static volatile GType id = 0;
149
150   if (g_once_init_enter ((gsize *) & id)) {
151     GType _id;
152
153     _id = g_enum_register_static ("GstWasapiDeviceRole", values);
154
155     g_once_init_leave ((gsize *) & id, _id);
156   }
157
158   return id;
159 }
160
161 gint
162 gst_wasapi_device_role_to_erole (gint role)
163 {
164   switch (role) {
165     case GST_WASAPI_DEVICE_ROLE_CONSOLE:
166       return eConsole;
167     case GST_WASAPI_DEVICE_ROLE_MULTIMEDIA:
168       return eMultimedia;
169     case GST_WASAPI_DEVICE_ROLE_COMMS:
170       return eCommunications;
171     default:
172       g_assert_not_reached ();
173   }
174 }
175
176 gint
177 gst_wasapi_erole_to_device_role (gint erole)
178 {
179   switch (erole) {
180     case eConsole:
181       return GST_WASAPI_DEVICE_ROLE_CONSOLE;
182     case eMultimedia:
183       return GST_WASAPI_DEVICE_ROLE_MULTIMEDIA;
184     case eCommunications:
185       return GST_WASAPI_DEVICE_ROLE_COMMS;
186     default:
187       g_assert_not_reached ();
188   }
189 }
190
191 static const gchar *
192 hresult_to_string_fallback (HRESULT hr)
193 {
194   const gchar *s = "unknown error";
195
196   switch (hr) {
197     case AUDCLNT_E_NOT_INITIALIZED:
198       s = "AUDCLNT_E_NOT_INITIALIZED";
199       break;
200     case AUDCLNT_E_ALREADY_INITIALIZED:
201       s = "AUDCLNT_E_ALREADY_INITIALIZED";
202       break;
203     case AUDCLNT_E_WRONG_ENDPOINT_TYPE:
204       s = "AUDCLNT_E_WRONG_ENDPOINT_TYPE";
205       break;
206     case AUDCLNT_E_DEVICE_INVALIDATED:
207       s = "AUDCLNT_E_DEVICE_INVALIDATED";
208       break;
209     case AUDCLNT_E_NOT_STOPPED:
210       s = "AUDCLNT_E_NOT_STOPPED";
211       break;
212     case AUDCLNT_E_BUFFER_TOO_LARGE:
213       s = "AUDCLNT_E_BUFFER_TOO_LARGE";
214       break;
215     case AUDCLNT_E_OUT_OF_ORDER:
216       s = "AUDCLNT_E_OUT_OF_ORDER";
217       break;
218     case AUDCLNT_E_UNSUPPORTED_FORMAT:
219       s = "AUDCLNT_E_UNSUPPORTED_FORMAT";
220       break;
221     case AUDCLNT_E_INVALID_DEVICE_PERIOD:
222       s = "AUDCLNT_E_INVALID_DEVICE_PERIOD";
223       break;
224     case AUDCLNT_E_INVALID_SIZE:
225       s = "AUDCLNT_E_INVALID_SIZE";
226       break;
227     case AUDCLNT_E_DEVICE_IN_USE:
228       s = "AUDCLNT_E_DEVICE_IN_USE";
229       break;
230     case AUDCLNT_E_BUFFER_OPERATION_PENDING:
231       s = "AUDCLNT_E_BUFFER_OPERATION_PENDING";
232       break;
233     case AUDCLNT_E_BUFFER_SIZE_ERROR:
234       s = "AUDCLNT_E_BUFFER_SIZE_ERROR";
235       break;
236     case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED:
237       s = "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED";
238       break;
239     case AUDCLNT_E_THREAD_NOT_REGISTERED:
240       s = "AUDCLNT_E_THREAD_NOT_REGISTERED";
241       break;
242     case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED:
243       s = "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED";
244       break;
245     case AUDCLNT_E_ENDPOINT_CREATE_FAILED:
246       s = "AUDCLNT_E_ENDPOINT_CREATE_FAILED";
247       break;
248     case AUDCLNT_E_SERVICE_NOT_RUNNING:
249       s = "AUDCLNT_E_SERVICE_NOT_RUNNING";
250       break;
251     case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED:
252       s = "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED";
253       break;
254     case AUDCLNT_E_EXCLUSIVE_MODE_ONLY:
255       s = "AUDCLNT_E_EXCLUSIVE_MODE_ONLY";
256       break;
257     case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL:
258       s = "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL";
259       break;
260     case AUDCLNT_E_EVENTHANDLE_NOT_SET:
261       s = "AUDCLNT_E_EVENTHANDLE_NOT_SET";
262       break;
263     case AUDCLNT_E_INCORRECT_BUFFER_SIZE:
264       s = "AUDCLNT_E_INCORRECT_BUFFER_SIZE";
265       break;
266     case AUDCLNT_E_CPUUSAGE_EXCEEDED:
267       s = "AUDCLNT_E_CPUUSAGE_EXCEEDED";
268       break;
269     case AUDCLNT_S_BUFFER_EMPTY:
270       s = "AUDCLNT_S_BUFFER_EMPTY";
271       break;
272     case AUDCLNT_S_THREAD_ALREADY_REGISTERED:
273       s = "AUDCLNT_S_THREAD_ALREADY_REGISTERED";
274       break;
275     case AUDCLNT_S_POSITION_STALLED:
276       s = "AUDCLNT_S_POSITION_STALLED";
277       break;
278     case E_POINTER:
279       s = "E_POINTER";
280       break;
281     case E_INVALIDARG:
282       s = "E_INVALIDARG";
283       break;
284   }
285
286   return s;
287 }
288
289 gchar *
290 gst_wasapi_util_hresult_to_string (HRESULT hr)
291 {
292   DWORD flags;
293   gchar *ret_text;
294   LPTSTR error_text = NULL;
295
296   flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
297       | FORMAT_MESSAGE_IGNORE_INSERTS;
298   FormatMessage (flags, NULL, hr, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
299       (LPTSTR) & error_text, 0, NULL);
300
301   /* If we couldn't get the error msg, try the fallback switch statement */
302   if (error_text == NULL)
303     return g_strdup (hresult_to_string_fallback (hr));
304
305 #ifdef UNICODE
306   /* If UNICODE is defined, LPTSTR is LPWSTR which is UTF-16 */
307   ret_text = g_utf16_to_utf8 (error_text, 0, NULL, NULL, NULL);
308 #else
309   ret_text = g_strdup (error_text);
310 #endif
311
312   LocalFree (error_text);
313   return ret_text;
314 }
315
316 static IMMDeviceEnumerator *
317 gst_wasapi_util_get_device_enumerator (GstElement * self)
318 {
319   HRESULT hr;
320   IMMDeviceEnumerator *enumerator = NULL;
321
322   hr = CoCreateInstance (&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
323       &IID_IMMDeviceEnumerator, (void **) &enumerator);
324   HR_FAILED_RET (hr, CoCreateInstance (MMDeviceEnumerator), NULL);
325
326   return enumerator;
327 }
328
329 gboolean
330 gst_wasapi_util_get_devices (GstElement * self, gboolean active,
331     GList ** devices)
332 {
333   gboolean res = FALSE;
334   static GstStaticCaps scaps = GST_STATIC_CAPS (GST_WASAPI_STATIC_CAPS);
335   DWORD dwStateMask = active ? DEVICE_STATE_ACTIVE : DEVICE_STATEMASK_ALL;
336   IMMDeviceCollection *device_collection = NULL;
337   IMMDeviceEnumerator *enumerator = NULL;
338   const gchar *device_class, *element_name;
339   guint ii, count;
340   HRESULT hr;
341
342   *devices = NULL;
343
344   enumerator = gst_wasapi_util_get_device_enumerator (self);
345   if (!enumerator)
346     return FALSE;
347
348   hr = IMMDeviceEnumerator_EnumAudioEndpoints (enumerator, eAll, dwStateMask,
349       &device_collection);
350   HR_FAILED_GOTO (hr, IMMDeviceEnumerator::EnumAudioEndpoints, err);
351
352   hr = IMMDeviceCollection_GetCount (device_collection, &count);
353   HR_FAILED_GOTO (hr, IMMDeviceCollection::GetCount, err);
354
355   /* Create a GList of GstDevices* to return */
356   for (ii = 0; ii < count; ii++) {
357     IMMDevice *item = NULL;
358     IMMEndpoint *endpoint = NULL;
359     IAudioClient *client = NULL;
360     IPropertyStore *prop_store = NULL;
361     WAVEFORMATEX *format = NULL;
362     gchar *description = NULL;
363     gchar *strid = NULL;
364     EDataFlow dataflow;
365     PROPVARIANT var;
366     wchar_t *wstrid;
367     GstDevice *device;
368     GstStructure *props;
369     GstCaps *caps;
370
371     hr = IMMDeviceCollection_Item (device_collection, ii, &item);
372     if (hr != S_OK)
373       continue;
374
375     hr = IMMDevice_QueryInterface (item, &IID_IMMEndpoint, (void **) &endpoint);
376     if (hr != S_OK)
377       goto next;
378
379     hr = IMMEndpoint_GetDataFlow (endpoint, &dataflow);
380     if (hr != S_OK)
381       goto next;
382
383     if (dataflow == eRender) {
384       device_class = "Audio/Sink";
385       element_name = "wasapisink";
386     } else {
387       device_class = "Audio/Source";
388       element_name = "wasapisrc";
389     }
390
391     PropVariantInit (&var);
392
393     hr = IMMDevice_GetId (item, &wstrid);
394     if (hr != S_OK)
395       goto next;
396     strid = g_utf16_to_utf8 (wstrid, -1, NULL, NULL, NULL);
397     CoTaskMemFree (wstrid);
398
399     hr = IMMDevice_OpenPropertyStore (item, STGM_READ, &prop_store);
400     if (hr != S_OK)
401       goto next;
402
403     /* NOTE: More properties can be added as needed from here:
404      * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370794(v=vs.85).aspx */
405     hr = IPropertyStore_GetValue (prop_store, &PKEY_Device_FriendlyName, &var);
406     if (hr != S_OK)
407       goto next;
408     description = g_utf16_to_utf8 (var.pwszVal, -1, NULL, NULL, NULL);
409     PropVariantClear (&var);
410
411     /* Get the audio client so we can fetch the mix format for shared mode
412      * to get the device format for exclusive mode (or something close to that)
413      * fetch PKEY_AudioEngine_DeviceFormat from the property store. */
414     hr = IMMDevice_Activate (item, &IID_IAudioClient, CLSCTX_ALL, NULL,
415         (void **) &client);
416     if (hr != S_OK) {
417       gchar *msg = gst_wasapi_util_hresult_to_string (hr);
418       GST_ERROR_OBJECT (self, "IMMDevice::Activate (IID_IAudioClient) failed"
419           "on %s: %s", strid, msg);
420       g_free (msg);
421       goto next;
422     }
423
424     hr = IAudioClient_GetMixFormat (client, &format);
425     if (hr != S_OK || format == NULL) {
426       gchar *msg = gst_wasapi_util_hresult_to_string (hr);
427       GST_ERROR_OBJECT (self, "GetMixFormat failed on %s: %s", strid, msg);
428       g_free (msg);
429       goto next;
430     }
431
432     if (!gst_wasapi_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format,
433             gst_static_caps_get (&scaps), &caps, NULL))
434       goto next;
435
436     /* Set some useful properties */
437     props = gst_structure_new ("wasapi-proplist",
438         "device.api", G_TYPE_STRING, "wasapi",
439         "device.strid", G_TYPE_STRING, GST_STR_NULL (strid),
440         "wasapi.device.description", G_TYPE_STRING, description, NULL);
441
442     device = g_object_new (GST_TYPE_WASAPI_DEVICE, "device", strid,
443         "display-name", description, "caps", caps,
444         "device-class", device_class, "properties", props, NULL);
445     GST_WASAPI_DEVICE (device)->element = element_name;
446
447     gst_structure_free (props);
448     gst_caps_unref (caps);
449     *devices = g_list_prepend (*devices, device);
450
451   next:
452     PropVariantClear (&var);
453     if (prop_store)
454       IUnknown_Release (prop_store);
455     if (endpoint)
456       IUnknown_Release (endpoint);
457     if (client)
458       IUnknown_Release (client);
459     if (item)
460       IUnknown_Release (item);
461     if (description)
462       g_free (description);
463     if (strid)
464       g_free (strid);
465   }
466
467   res = TRUE;
468
469 err:
470   if (enumerator)
471     IUnknown_Release (enumerator);
472   if (device_collection)
473     IUnknown_Release (device_collection);
474   return res;
475 }
476
477 gboolean
478 gst_wasapi_util_get_device_format (GstElement * self,
479     gint device_mode, IMMDevice * device, IAudioClient * client,
480     WAVEFORMATEX ** ret_format)
481 {
482   WAVEFORMATEX *format;
483   HRESULT hr;
484
485   *ret_format = NULL;
486
487   hr = IAudioClient_GetMixFormat (client, &format);
488   HR_FAILED_RET (hr, IAudioClient::GetMixFormat, FALSE);
489
490   /* WASAPI always accepts the format returned by GetMixFormat in shared mode */
491   if (device_mode == AUDCLNT_SHAREMODE_SHARED)
492     goto out;
493
494   /* WASAPI may or may not support this format in exclusive mode */
495   hr = IAudioClient_IsFormatSupported (client, AUDCLNT_SHAREMODE_EXCLUSIVE,
496       format, NULL);
497   if (hr == S_OK)
498     goto out;
499
500   CoTaskMemFree (format);
501
502   /* Open the device property store, and get the format that WASAPI has been
503    * using for sending data to the device */
504   {
505     PROPVARIANT var;
506     IPropertyStore *prop_store = NULL;
507
508     hr = IMMDevice_OpenPropertyStore (device, STGM_READ, &prop_store);
509     HR_FAILED_RET (hr, IMMDevice::OpenPropertyStore, FALSE);
510
511     hr = IPropertyStore_GetValue (prop_store, &PKEY_AudioEngine_DeviceFormat,
512         &var);
513     if (hr != S_OK) {
514       gchar *msg = gst_wasapi_util_hresult_to_string (hr);
515       GST_ERROR_OBJECT (self, "GetValue failed: %s", msg);
516       g_free (msg);
517       IUnknown_Release (prop_store);
518       return FALSE;
519     }
520
521     format = malloc (var.blob.cbSize);
522     memcpy (format, var.blob.pBlobData, var.blob.cbSize);
523
524     PropVariantClear (&var);
525     IUnknown_Release (prop_store);
526   }
527
528   /* WASAPI may or may not support this format in exclusive mode */
529   hr = IAudioClient_IsFormatSupported (client, AUDCLNT_SHAREMODE_EXCLUSIVE,
530       format, NULL);
531   if (hr == S_OK)
532     goto out;
533
534   GST_ERROR_OBJECT (self, "AudioEngine DeviceFormat not supported");
535   free (format);
536   return FALSE;
537
538 out:
539   *ret_format = format;
540   return TRUE;
541 }
542
543 gboolean
544 gst_wasapi_util_get_device_client (GstElement * self,
545     gboolean capture, gint role, const wchar_t * device_strid,
546     IMMDevice ** ret_device, IAudioClient ** ret_client)
547 {
548   gboolean res = FALSE;
549   HRESULT hr;
550   IMMDeviceEnumerator *enumerator = NULL;
551   IMMDevice *device = NULL;
552   IAudioClient *client = NULL;
553
554   if (!(enumerator = gst_wasapi_util_get_device_enumerator (self)))
555     goto beach;
556
557   if (!device_strid) {
558     hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enumerator,
559         capture ? eCapture : eRender, role, &device);
560     HR_FAILED_GOTO (hr, IMMDeviceEnumerator::GetDefaultAudioEndpoint, beach);
561   } else {
562     hr = IMMDeviceEnumerator_GetDevice (enumerator, device_strid, &device);
563     if (hr != S_OK) {
564       gchar *msg = gst_wasapi_util_hresult_to_string (hr);
565       GST_ERROR_OBJECT (self, "IMMDeviceEnumerator::GetDevice (%S) failed"
566           ": %s", device_strid, msg);
567       g_free (msg);
568       goto beach;
569     }
570   }
571
572   if (gst_wasapi_util_have_audioclient3 ())
573     hr = IMMDevice_Activate (device, &IID_IAudioClient3, CLSCTX_ALL, NULL,
574         (void **) &client);
575   else
576     hr = IMMDevice_Activate (device, &IID_IAudioClient, CLSCTX_ALL, NULL,
577         (void **) &client);
578   HR_FAILED_GOTO (hr, IMMDevice::Activate (IID_IAudioClient), beach);
579
580   IUnknown_AddRef (client);
581   IUnknown_AddRef (device);
582   *ret_client = client;
583   *ret_device = device;
584
585   res = TRUE;
586
587 beach:
588   if (client != NULL)
589     IUnknown_Release (client);
590
591   if (device != NULL)
592     IUnknown_Release (device);
593
594   if (enumerator != NULL)
595     IUnknown_Release (enumerator);
596
597   return res;
598 }
599
600 gboolean
601 gst_wasapi_util_get_render_client (GstElement * self, IAudioClient * client,
602     IAudioRenderClient ** ret_render_client)
603 {
604   gboolean res = FALSE;
605   HRESULT hr;
606   IAudioRenderClient *render_client = NULL;
607
608   hr = IAudioClient_GetService (client, &IID_IAudioRenderClient,
609       (void **) &render_client);
610   HR_FAILED_GOTO (hr, IAudioClient::GetService, beach);
611
612   *ret_render_client = render_client;
613   res = TRUE;
614
615 beach:
616   return res;
617 }
618
619 gboolean
620 gst_wasapi_util_get_capture_client (GstElement * self, IAudioClient * client,
621     IAudioCaptureClient ** ret_capture_client)
622 {
623   gboolean res = FALSE;
624   HRESULT hr;
625   IAudioCaptureClient *capture_client = NULL;
626
627   hr = IAudioClient_GetService (client, &IID_IAudioCaptureClient,
628       (void **) &capture_client);
629   HR_FAILED_GOTO (hr, IAudioClient::GetService, beach);
630
631   *ret_capture_client = capture_client;
632   res = TRUE;
633
634 beach:
635   return res;
636 }
637
638 gboolean
639 gst_wasapi_util_get_clock (GstElement * self, IAudioClient * client,
640     IAudioClock ** ret_clock)
641 {
642   gboolean res = FALSE;
643   HRESULT hr;
644   IAudioClock *clock = NULL;
645
646   hr = IAudioClient_GetService (client, &IID_IAudioClock, (void **) &clock);
647   HR_FAILED_GOTO (hr, IAudioClient::GetService, beach);
648
649   *ret_clock = clock;
650   res = TRUE;
651
652 beach:
653   return res;
654 }
655
656 static const gchar *
657 gst_waveformatex_to_audio_format (WAVEFORMATEXTENSIBLE * format)
658 {
659   const gchar *fmt_str = NULL;
660   GstAudioFormat fmt = GST_AUDIO_FORMAT_UNKNOWN;
661
662   if (format->Format.wFormatTag == WAVE_FORMAT_PCM) {
663     fmt = gst_audio_format_build_integer (TRUE, G_LITTLE_ENDIAN,
664         format->Format.wBitsPerSample, format->Format.wBitsPerSample);
665   } else if (format->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
666     if (format->Format.wBitsPerSample == 32)
667       fmt = GST_AUDIO_FORMAT_F32LE;
668     else if (format->Format.wBitsPerSample == 64)
669       fmt = GST_AUDIO_FORMAT_F64LE;
670   } else if (format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
671     if (IsEqualGUID (&format->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) {
672       fmt = gst_audio_format_build_integer (TRUE, G_LITTLE_ENDIAN,
673           format->Format.wBitsPerSample, format->Samples.wValidBitsPerSample);
674     } else if (IsEqualGUID (&format->SubFormat,
675             &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) {
676       if (format->Format.wBitsPerSample == 32
677           && format->Samples.wValidBitsPerSample == 32)
678         fmt = GST_AUDIO_FORMAT_F32LE;
679       else if (format->Format.wBitsPerSample == 64 &&
680           format->Samples.wValidBitsPerSample == 64)
681         fmt = GST_AUDIO_FORMAT_F64LE;
682     }
683   }
684
685   if (fmt != GST_AUDIO_FORMAT_UNKNOWN)
686     fmt_str = gst_audio_format_to_string (fmt);
687
688   return fmt_str;
689 }
690
691 static void
692 gst_wasapi_util_channel_position_all_none (guint channels,
693     GstAudioChannelPosition * position)
694 {
695   int ii;
696   for (ii = 0; ii < channels; ii++)
697     position[ii] = GST_AUDIO_CHANNEL_POSITION_NONE;
698 }
699
700 /* Parse WAVEFORMATEX to get the gstreamer channel mask, and the wasapi channel
701  * positions so GstAudioRingbuffer can reorder the audio data to match the
702  * gstreamer channel order. */
703 static guint64
704 gst_wasapi_util_waveformatex_to_channel_mask (WAVEFORMATEXTENSIBLE * format,
705     GstAudioChannelPosition ** out_position)
706 {
707   int ii;
708   guint64 mask = 0;
709   WORD nChannels = format->Format.nChannels;
710   DWORD dwChannelMask = format->dwChannelMask;
711   GstAudioChannelPosition *pos = NULL;
712
713   pos = g_new (GstAudioChannelPosition, nChannels);
714   gst_wasapi_util_channel_position_all_none (nChannels, pos);
715
716   /* Too many channels, have to assume that they are all non-positional */
717   if (nChannels > G_N_ELEMENTS (wasapi_to_gst_pos)) {
718     GST_INFO ("Got too many (%i) channels, assuming non-positional", nChannels);
719     goto out;
720   }
721
722   /* Too many bits in the channel mask, and the bits don't match nChannels */
723   if (dwChannelMask >> (G_N_ELEMENTS (wasapi_to_gst_pos) + 1) != 0) {
724     GST_WARNING ("Too many bits in channel mask (%lu), assuming "
725         "non-positional", dwChannelMask);
726     goto out;
727   }
728
729   /* Map WASAPI's channel mask to Gstreamer's channel mask and positions.
730    * If the no. of bits in the mask > nChannels, we will ignore the extra. */
731   for (ii = 0; ii < nChannels; ii++) {
732     if (!(dwChannelMask & wasapi_to_gst_pos[ii].wasapi_pos))
733       /* Non-positional or unknown position, warn? */
734       continue;
735     mask |= G_GUINT64_CONSTANT (1) << wasapi_to_gst_pos[ii].gst_pos;
736     pos[ii] = wasapi_to_gst_pos[ii].gst_pos;
737   }
738
739 out:
740   if (out_position)
741     *out_position = pos;
742   return mask;
743 }
744
745 gboolean
746 gst_wasapi_util_parse_waveformatex (WAVEFORMATEXTENSIBLE * format,
747     GstCaps * template_caps, GstCaps ** out_caps,
748     GstAudioChannelPosition ** out_positions)
749 {
750   int ii;
751   const gchar *afmt;
752   guint64 channel_mask;
753
754   *out_caps = NULL;
755
756   /* TODO: handle SPDIF and other encoded formats */
757
758   /* 1 or 2 channels <= 16 bits sample size OR
759    * 1 or 2 channels > 16 bits sample size or >2 channels */
760   if (format->Format.wFormatTag != WAVE_FORMAT_PCM &&
761       format->Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT &&
762       format->Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
763     /* Unhandled format tag */
764     return FALSE;
765
766   /* WASAPI can only tell us one canonical mix format that it will accept. The
767    * alternative is calling IsFormatSupported on all combinations of formats.
768    * Instead, it's simpler and faster to require conversion inside gstreamer */
769   afmt = gst_waveformatex_to_audio_format (format);
770   if (afmt == NULL)
771     return FALSE;
772
773   *out_caps = gst_caps_copy (template_caps);
774
775   /* This will always return something that might be usable */
776   channel_mask =
777       gst_wasapi_util_waveformatex_to_channel_mask (format, out_positions);
778
779   for (ii = 0; ii < gst_caps_get_size (*out_caps); ii++) {
780     GstStructure *s = gst_caps_get_structure (*out_caps, ii);
781
782     gst_structure_set (s,
783         "format", G_TYPE_STRING, afmt,
784         "channels", G_TYPE_INT, format->Format.nChannels,
785         "rate", G_TYPE_INT, format->Format.nSamplesPerSec,
786         "channel-mask", GST_TYPE_BITMASK, channel_mask, NULL);
787   }
788
789   return TRUE;
790 }
791
792 void
793 gst_wasapi_util_get_best_buffer_sizes (GstAudioRingBufferSpec * spec,
794     gboolean exclusive, REFERENCE_TIME default_period,
795     REFERENCE_TIME min_period, REFERENCE_TIME * ret_period,
796     REFERENCE_TIME * ret_buffer_duration)
797 {
798   REFERENCE_TIME use_period, use_buffer;
799
800   /* Figure out what integral device period to use as the base */
801   if (exclusive) {
802     /* Exclusive mode can run at multiples of either the minimum period or the
803      * default period; these are on the hardware ringbuffer */
804     if (spec->latency_time * 10 > default_period)
805       use_period = default_period;
806     else
807       use_period = min_period;
808   } else {
809     /* Shared mode always runs at the default period, so if we want a larger
810      * period (for lower CPU usage), we do it as a multiple of that */
811     use_period = default_period;
812   }
813
814   /* Ensure that the period (latency_time) used is an integral multiple of
815    * either the default period or the minimum period */
816   use_period = use_period * MAX ((spec->latency_time * 10) / use_period, 1);
817
818   if (exclusive) {
819     /* Buffer duration is the same as the period in exclusive mode. The
820      * hardware is always writing out one buffer (of size *ret_period), and
821      * we're writing to the other one. */
822     use_buffer = use_period;
823   } else {
824     /* Ask WASAPI to create a software ringbuffer of at least this size; it may
825      * be larger so the actual buffer time may be different, which is why after
826      * initialization we read the buffer duration actually in-use and set
827      * segsize/segtotal from that. */
828     use_buffer = spec->buffer_time * 10;
829     /* Has to be at least twice the period */
830     if (use_buffer < 2 * use_period)
831       use_buffer = 2 * use_period;
832   }
833
834   *ret_period = use_period;
835   *ret_buffer_duration = use_buffer;
836 }
837
838 gboolean
839 gst_wasapi_util_initialize_audioclient (GstElement * self,
840     GstAudioRingBufferSpec * spec, IAudioClient * client,
841     WAVEFORMATEX * format, guint sharemode, gboolean low_latency,
842     guint * ret_devicep_frames)
843 {
844   REFERENCE_TIME default_period, min_period;
845   REFERENCE_TIME device_period, device_buffer_duration;
846   guint rate;
847   HRESULT hr;
848
849   hr = IAudioClient_GetDevicePeriod (client, &default_period, &min_period);
850   HR_FAILED_RET (hr, IAudioClient::GetDevicePeriod, FALSE);
851
852   GST_INFO_OBJECT (self, "wasapi default period: %" G_GINT64_FORMAT
853       ", min period: %" G_GINT64_FORMAT, default_period, min_period);
854
855   rate = GST_AUDIO_INFO_RATE (&spec->info);
856
857   if (low_latency) {
858     if (sharemode == AUDCLNT_SHAREMODE_SHARED) {
859       device_period = default_period;
860       device_buffer_duration = 0;
861     } else {
862       device_period = min_period;
863       device_buffer_duration = min_period;
864     }
865   } else {
866     /* Clamp values to integral multiples of an appropriate period */
867     gst_wasapi_util_get_best_buffer_sizes (spec,
868         sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE, default_period,
869         min_period, &device_period, &device_buffer_duration);
870   }
871
872   /* For some reason, we need to call this a second time for exclusive mode */
873   if (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE)
874     CoInitialize (NULL);
875
876   hr = IAudioClient_Initialize (client, sharemode,
877       AUDCLNT_STREAMFLAGS_EVENTCALLBACK, device_buffer_duration,
878       /* This must always be 0 in shared mode */
879       sharemode == AUDCLNT_SHAREMODE_SHARED ? 0 : device_period, format, NULL);
880
881   if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED &&
882       sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
883     guint32 n_frames;
884
885     GST_WARNING_OBJECT (self, "initialize failed due to unaligned period %i",
886         (int) device_period);
887
888     /* Calculate a new aligned period. First get the aligned buffer size. */
889     hr = IAudioClient_GetBufferSize (client, &n_frames);
890     HR_FAILED_RET (hr, IAudioClient::GetBufferSize, FALSE);
891
892     device_period = (GST_SECOND / 100) * n_frames / rate;
893
894     GST_WARNING_OBJECT (self, "trying to re-initialize with period %i "
895         "(%i frames, %i rate)", (int) device_period, n_frames, rate);
896
897     hr = IAudioClient_Initialize (client, sharemode,
898         AUDCLNT_STREAMFLAGS_EVENTCALLBACK, device_period,
899         device_period, format, NULL);
900   }
901   HR_FAILED_RET (hr, IAudioClient::Initialize, FALSE);
902
903   *ret_devicep_frames = (rate * device_period * 100) / GST_SECOND;
904
905   return TRUE;
906 }
907
908 gboolean
909 gst_wasapi_util_initialize_audioclient3 (GstElement * self,
910     GstAudioRingBufferSpec * spec, IAudioClient3 * client,
911     WAVEFORMATEX * format, gboolean low_latency, guint * ret_devicep_frames)
912 {
913   HRESULT hr;
914   guint rate, devicep_frames;
915   guint defaultp_frames, fundp_frames, minp_frames, maxp_frames;
916   WAVEFORMATEX *tmpf;
917
918   rate = GST_AUDIO_INFO_RATE (&spec->info);
919
920   hr = IAudioClient3_GetSharedModeEnginePeriod (client, format,
921       &defaultp_frames, &fundp_frames, &minp_frames, &maxp_frames);
922   HR_FAILED_RET (hr, IAudioClient3::GetSharedModeEnginePeriod, FALSE);
923
924   GST_INFO_OBJECT (self, "Using IAudioClient3, default period %i frames, "
925       "fundamental period %i frames, minimum period %i frames, maximum period "
926       "%i frames", defaultp_frames, fundp_frames, minp_frames, maxp_frames);
927
928   if (low_latency) {
929     devicep_frames = minp_frames;
930   } else {
931     /* rate is in Hz, latency_time is in usec */
932     int tmp = (rate * spec->latency_time * GST_USECOND) / GST_SECOND;
933     devicep_frames = CLAMP (tmp, minp_frames, maxp_frames);
934     /* Ensure it's a multiple of the fundamental period */
935     tmp = devicep_frames / fundp_frames;
936     devicep_frames = tmp * fundp_frames;
937   }
938
939   hr = IAudioClient3_InitializeSharedAudioStream (client,
940       AUDCLNT_STREAMFLAGS_EVENTCALLBACK, devicep_frames, format, NULL);
941   HR_FAILED_RET (hr, IAudioClient3::InitializeSharedAudioStream, FALSE);
942
943   hr = IAudioClient3_GetCurrentSharedModeEnginePeriod (client, &tmpf,
944       &devicep_frames);
945   CoTaskMemFree (tmpf);
946   HR_FAILED_RET (hr, IAudioClient3::GetCurrentSharedModeEnginePeriod, FALSE);
947
948   *ret_devicep_frames = devicep_frames;
949   return TRUE;
950 }
951
952 static gboolean
953 gst_wasapi_util_init_thread_priority (void)
954 {
955   if (gst_wasapi_avrt_tbl.tried_loading)
956     return gst_wasapi_avrt_tbl.dll != NULL;
957
958   if (!gst_wasapi_avrt_tbl.dll)
959     gst_wasapi_avrt_tbl.dll = LoadLibrary (TEXT ("avrt.dll"));
960
961   if (!gst_wasapi_avrt_tbl.dll) {
962     GST_WARNING ("Failed to set thread priority, can't find avrt.dll");
963     gst_wasapi_avrt_tbl.tried_loading = TRUE;
964     return FALSE;
965   }
966
967   gst_wasapi_avrt_tbl.AvSetMmThreadCharacteristics =
968       GetProcAddress (gst_wasapi_avrt_tbl.dll, "AvSetMmThreadCharacteristicsA");
969   gst_wasapi_avrt_tbl.AvRevertMmThreadCharacteristics =
970       GetProcAddress (gst_wasapi_avrt_tbl.dll,
971       "AvRevertMmThreadCharacteristics");
972
973   gst_wasapi_avrt_tbl.tried_loading = TRUE;
974
975   return TRUE;
976 }
977
978 HANDLE
979 gst_wasapi_util_set_thread_characteristics (void)
980 {
981   DWORD taskIndex = 0;
982
983   if (!gst_wasapi_util_init_thread_priority ())
984     return NULL;
985
986   return gst_wasapi_avrt_tbl.AvSetMmThreadCharacteristics (TEXT ("Pro Audio"),
987       &taskIndex);
988 }
989
990 void
991 gst_wasapi_util_revert_thread_characteristics (HANDLE handle)
992 {
993   if (!gst_wasapi_util_init_thread_priority ())
994     return;
995
996   gst_wasapi_avrt_tbl.AvRevertMmThreadCharacteristics (handle);
997 }