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>
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.
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.
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.
25 #include "gstwasapiutil.h"
26 #include "gstwasapidevice.h"
28 GST_DEBUG_CATEGORY_EXTERN (gst_wasapi_debug);
29 #define GST_CAT_DEFAULT gst_wasapi_debug
31 /* This was only added to MinGW in ~2015 and our Cerbero toolchain is too old */
33 #include <functiondiscoverykeys_devpkey.h>
34 #elif !defined(PKEY_Device_FriendlyName)
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);
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}
48 const IID IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35,
49 {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6}
52 const IID IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,
53 {0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5}
56 const IID IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32,
57 {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2}
60 const IID IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4,
61 {0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42}
64 const IID IID_IAudioClock = { 0xcd63314f, 0x3fba, 0x4a1b,
65 {0x81, 0x2c, 0xef, 0x96, 0x35, 0x87, 0x28, 0xe7}
68 const IID IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0,
69 {0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17}
72 const IID IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483,
73 {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2}
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}
106 static int windows_major_version = 0;
111 gboolean tried_loading;
113 HANDLE (WINAPI * AvSetMmThreadCharacteristics) (LPCSTR, LPDWORD);
114 BOOL (WINAPI * AvRevertMmThreadCharacteristics) (HANDLE);
115 } gst_wasapi_avrt_tbl = {
119 gst_wasapi_util_have_audioclient3 (void)
121 if (windows_major_version > 0)
122 return windows_major_version == 10;
124 if (g_getenv ("GST_WASAPI_DISABLE_AUDIOCLIENT3") != NULL) {
125 windows_major_version = 6;
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;
134 return windows_major_version == 10;
138 gst_wasapi_device_role_get_type (void)
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",
145 {GST_WASAPI_DEVICE_ROLE_COMMS, "Voice communications", "comms"},
148 static volatile GType id = 0;
150 if (g_once_init_enter ((gsize *) & id)) {
153 _id = g_enum_register_static ("GstWasapiDeviceRole", values);
155 g_once_init_leave ((gsize *) & id, _id);
162 gst_wasapi_device_role_to_erole (gint role)
165 case GST_WASAPI_DEVICE_ROLE_CONSOLE:
167 case GST_WASAPI_DEVICE_ROLE_MULTIMEDIA:
169 case GST_WASAPI_DEVICE_ROLE_COMMS:
170 return eCommunications;
172 g_assert_not_reached ();
177 gst_wasapi_erole_to_device_role (gint erole)
181 return GST_WASAPI_DEVICE_ROLE_CONSOLE;
183 return GST_WASAPI_DEVICE_ROLE_MULTIMEDIA;
184 case eCommunications:
185 return GST_WASAPI_DEVICE_ROLE_COMMS;
187 g_assert_not_reached ();
192 hresult_to_string_fallback (HRESULT hr)
194 const gchar *s = "unknown error";
197 case AUDCLNT_E_NOT_INITIALIZED:
198 s = "AUDCLNT_E_NOT_INITIALIZED";
200 case AUDCLNT_E_ALREADY_INITIALIZED:
201 s = "AUDCLNT_E_ALREADY_INITIALIZED";
203 case AUDCLNT_E_WRONG_ENDPOINT_TYPE:
204 s = "AUDCLNT_E_WRONG_ENDPOINT_TYPE";
206 case AUDCLNT_E_DEVICE_INVALIDATED:
207 s = "AUDCLNT_E_DEVICE_INVALIDATED";
209 case AUDCLNT_E_NOT_STOPPED:
210 s = "AUDCLNT_E_NOT_STOPPED";
212 case AUDCLNT_E_BUFFER_TOO_LARGE:
213 s = "AUDCLNT_E_BUFFER_TOO_LARGE";
215 case AUDCLNT_E_OUT_OF_ORDER:
216 s = "AUDCLNT_E_OUT_OF_ORDER";
218 case AUDCLNT_E_UNSUPPORTED_FORMAT:
219 s = "AUDCLNT_E_UNSUPPORTED_FORMAT";
221 case AUDCLNT_E_INVALID_DEVICE_PERIOD:
222 s = "AUDCLNT_E_INVALID_DEVICE_PERIOD";
224 case AUDCLNT_E_INVALID_SIZE:
225 s = "AUDCLNT_E_INVALID_SIZE";
227 case AUDCLNT_E_DEVICE_IN_USE:
228 s = "AUDCLNT_E_DEVICE_IN_USE";
230 case AUDCLNT_E_BUFFER_OPERATION_PENDING:
231 s = "AUDCLNT_E_BUFFER_OPERATION_PENDING";
233 case AUDCLNT_E_BUFFER_SIZE_ERROR:
234 s = "AUDCLNT_E_BUFFER_SIZE_ERROR";
236 case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED:
237 s = "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED";
239 case AUDCLNT_E_THREAD_NOT_REGISTERED:
240 s = "AUDCLNT_E_THREAD_NOT_REGISTERED";
242 case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED:
243 s = "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED";
245 case AUDCLNT_E_ENDPOINT_CREATE_FAILED:
246 s = "AUDCLNT_E_ENDPOINT_CREATE_FAILED";
248 case AUDCLNT_E_SERVICE_NOT_RUNNING:
249 s = "AUDCLNT_E_SERVICE_NOT_RUNNING";
251 case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED:
252 s = "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED";
254 case AUDCLNT_E_EXCLUSIVE_MODE_ONLY:
255 s = "AUDCLNT_E_EXCLUSIVE_MODE_ONLY";
257 case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL:
258 s = "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL";
260 case AUDCLNT_E_EVENTHANDLE_NOT_SET:
261 s = "AUDCLNT_E_EVENTHANDLE_NOT_SET";
263 case AUDCLNT_E_INCORRECT_BUFFER_SIZE:
264 s = "AUDCLNT_E_INCORRECT_BUFFER_SIZE";
266 case AUDCLNT_E_CPUUSAGE_EXCEEDED:
267 s = "AUDCLNT_E_CPUUSAGE_EXCEEDED";
269 case AUDCLNT_S_BUFFER_EMPTY:
270 s = "AUDCLNT_S_BUFFER_EMPTY";
272 case AUDCLNT_S_THREAD_ALREADY_REGISTERED:
273 s = "AUDCLNT_S_THREAD_ALREADY_REGISTERED";
275 case AUDCLNT_S_POSITION_STALLED:
276 s = "AUDCLNT_S_POSITION_STALLED";
290 gst_wasapi_util_hresult_to_string (HRESULT hr)
294 LPTSTR error_text = NULL;
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);
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));
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);
309 ret_text = g_strdup (error_text);
312 LocalFree (error_text);
316 static IMMDeviceEnumerator *
317 gst_wasapi_util_get_device_enumerator (GstElement * self)
320 IMMDeviceEnumerator *enumerator = NULL;
322 hr = CoCreateInstance (&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
323 &IID_IMMDeviceEnumerator, (void **) &enumerator);
324 HR_FAILED_RET (hr, CoCreateInstance (MMDeviceEnumerator), NULL);
330 gst_wasapi_util_get_devices (GstElement * self, gboolean active,
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;
344 enumerator = gst_wasapi_util_get_device_enumerator (self);
348 hr = IMMDeviceEnumerator_EnumAudioEndpoints (enumerator, eAll, dwStateMask,
350 HR_FAILED_GOTO (hr, IMMDeviceEnumerator::EnumAudioEndpoints, err);
352 hr = IMMDeviceCollection_GetCount (device_collection, &count);
353 HR_FAILED_GOTO (hr, IMMDeviceCollection::GetCount, err);
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;
371 hr = IMMDeviceCollection_Item (device_collection, ii, &item);
375 hr = IMMDevice_QueryInterface (item, &IID_IMMEndpoint, (void **) &endpoint);
379 hr = IMMEndpoint_GetDataFlow (endpoint, &dataflow);
383 if (dataflow == eRender) {
384 device_class = "Audio/Sink";
385 element_name = "wasapisink";
387 device_class = "Audio/Source";
388 element_name = "wasapisrc";
391 PropVariantInit (&var);
393 hr = IMMDevice_GetId (item, &wstrid);
396 strid = g_utf16_to_utf8 (wstrid, -1, NULL, NULL, NULL);
397 CoTaskMemFree (wstrid);
399 hr = IMMDevice_OpenPropertyStore (item, STGM_READ, &prop_store);
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);
408 description = g_utf16_to_utf8 (var.pwszVal, -1, NULL, NULL, NULL);
409 PropVariantClear (&var);
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,
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);
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);
432 if (!gst_wasapi_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format,
433 gst_static_caps_get (&scaps), &caps, NULL))
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);
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;
447 gst_structure_free (props);
448 gst_caps_unref (caps);
449 *devices = g_list_prepend (*devices, device);
452 PropVariantClear (&var);
454 IUnknown_Release (prop_store);
456 IUnknown_Release (endpoint);
458 IUnknown_Release (client);
460 IUnknown_Release (item);
462 g_free (description);
471 IUnknown_Release (enumerator);
472 if (device_collection)
473 IUnknown_Release (device_collection);
478 gst_wasapi_util_get_device_format (GstElement * self,
479 gint device_mode, IMMDevice * device, IAudioClient * client,
480 WAVEFORMATEX ** ret_format)
482 WAVEFORMATEX *format;
487 hr = IAudioClient_GetMixFormat (client, &format);
488 HR_FAILED_RET (hr, IAudioClient::GetMixFormat, FALSE);
490 /* WASAPI always accepts the format returned by GetMixFormat in shared mode */
491 if (device_mode == AUDCLNT_SHAREMODE_SHARED)
494 /* WASAPI may or may not support this format in exclusive mode */
495 hr = IAudioClient_IsFormatSupported (client, AUDCLNT_SHAREMODE_EXCLUSIVE,
500 CoTaskMemFree (format);
502 /* Open the device property store, and get the format that WASAPI has been
503 * using for sending data to the device */
506 IPropertyStore *prop_store = NULL;
508 hr = IMMDevice_OpenPropertyStore (device, STGM_READ, &prop_store);
509 HR_FAILED_RET (hr, IMMDevice::OpenPropertyStore, FALSE);
511 hr = IPropertyStore_GetValue (prop_store, &PKEY_AudioEngine_DeviceFormat,
514 gchar *msg = gst_wasapi_util_hresult_to_string (hr);
515 GST_ERROR_OBJECT (self, "GetValue failed: %s", msg);
517 IUnknown_Release (prop_store);
521 format = malloc (var.blob.cbSize);
522 memcpy (format, var.blob.pBlobData, var.blob.cbSize);
524 PropVariantClear (&var);
525 IUnknown_Release (prop_store);
528 /* WASAPI may or may not support this format in exclusive mode */
529 hr = IAudioClient_IsFormatSupported (client, AUDCLNT_SHAREMODE_EXCLUSIVE,
534 GST_ERROR_OBJECT (self, "AudioEngine DeviceFormat not supported");
539 *ret_format = format;
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)
548 gboolean res = FALSE;
550 IMMDeviceEnumerator *enumerator = NULL;
551 IMMDevice *device = NULL;
552 IAudioClient *client = NULL;
554 if (!(enumerator = gst_wasapi_util_get_device_enumerator (self)))
558 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enumerator,
559 capture ? eCapture : eRender, role, &device);
560 HR_FAILED_GOTO (hr, IMMDeviceEnumerator::GetDefaultAudioEndpoint, beach);
562 hr = IMMDeviceEnumerator_GetDevice (enumerator, device_strid, &device);
564 gchar *msg = gst_wasapi_util_hresult_to_string (hr);
565 GST_ERROR_OBJECT (self, "IMMDeviceEnumerator::GetDevice (%S) failed"
566 ": %s", device_strid, msg);
572 if (gst_wasapi_util_have_audioclient3 ())
573 hr = IMMDevice_Activate (device, &IID_IAudioClient3, CLSCTX_ALL, NULL,
576 hr = IMMDevice_Activate (device, &IID_IAudioClient, CLSCTX_ALL, NULL,
578 HR_FAILED_GOTO (hr, IMMDevice::Activate (IID_IAudioClient), beach);
580 IUnknown_AddRef (client);
581 IUnknown_AddRef (device);
582 *ret_client = client;
583 *ret_device = device;
589 IUnknown_Release (client);
592 IUnknown_Release (device);
594 if (enumerator != NULL)
595 IUnknown_Release (enumerator);
601 gst_wasapi_util_get_render_client (GstElement * self, IAudioClient * client,
602 IAudioRenderClient ** ret_render_client)
604 gboolean res = FALSE;
606 IAudioRenderClient *render_client = NULL;
608 hr = IAudioClient_GetService (client, &IID_IAudioRenderClient,
609 (void **) &render_client);
610 HR_FAILED_GOTO (hr, IAudioClient::GetService, beach);
612 *ret_render_client = render_client;
620 gst_wasapi_util_get_capture_client (GstElement * self, IAudioClient * client,
621 IAudioCaptureClient ** ret_capture_client)
623 gboolean res = FALSE;
625 IAudioCaptureClient *capture_client = NULL;
627 hr = IAudioClient_GetService (client, &IID_IAudioCaptureClient,
628 (void **) &capture_client);
629 HR_FAILED_GOTO (hr, IAudioClient::GetService, beach);
631 *ret_capture_client = capture_client;
639 gst_wasapi_util_get_clock (GstElement * self, IAudioClient * client,
640 IAudioClock ** ret_clock)
642 gboolean res = FALSE;
644 IAudioClock *clock = NULL;
646 hr = IAudioClient_GetService (client, &IID_IAudioClock, (void **) &clock);
647 HR_FAILED_GOTO (hr, IAudioClient::GetService, beach);
657 gst_waveformatex_to_audio_format (WAVEFORMATEXTENSIBLE * format)
659 const gchar *fmt_str = NULL;
660 GstAudioFormat fmt = GST_AUDIO_FORMAT_UNKNOWN;
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;
685 if (fmt != GST_AUDIO_FORMAT_UNKNOWN)
686 fmt_str = gst_audio_format_to_string (fmt);
692 gst_wasapi_util_channel_position_all_none (guint channels,
693 GstAudioChannelPosition * position)
696 for (ii = 0; ii < channels; ii++)
697 position[ii] = GST_AUDIO_CHANNEL_POSITION_NONE;
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. */
704 gst_wasapi_util_waveformatex_to_channel_mask (WAVEFORMATEXTENSIBLE * format,
705 GstAudioChannelPosition ** out_position)
709 WORD nChannels = format->Format.nChannels;
710 DWORD dwChannelMask = format->dwChannelMask;
711 GstAudioChannelPosition *pos = NULL;
713 pos = g_new (GstAudioChannelPosition, nChannels);
714 gst_wasapi_util_channel_position_all_none (nChannels, pos);
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);
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);
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? */
735 mask |= G_GUINT64_CONSTANT (1) << wasapi_to_gst_pos[ii].gst_pos;
736 pos[ii] = wasapi_to_gst_pos[ii].gst_pos;
746 gst_wasapi_util_parse_waveformatex (WAVEFORMATEXTENSIBLE * format,
747 GstCaps * template_caps, GstCaps ** out_caps,
748 GstAudioChannelPosition ** out_positions)
752 guint64 channel_mask;
756 /* TODO: handle SPDIF and other encoded formats */
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 */
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);
773 *out_caps = gst_caps_copy (template_caps);
775 /* This will always return something that might be usable */
777 gst_wasapi_util_waveformatex_to_channel_mask (format, out_positions);
779 for (ii = 0; ii < gst_caps_get_size (*out_caps); ii++) {
780 GstStructure *s = gst_caps_get_structure (*out_caps, ii);
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);
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)
798 REFERENCE_TIME use_period, use_buffer;
800 /* Figure out what integral device period to use as the base */
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;
807 use_period = min_period;
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;
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);
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;
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;
834 *ret_period = use_period;
835 *ret_buffer_duration = use_buffer;
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)
844 REFERENCE_TIME default_period, min_period;
845 REFERENCE_TIME device_period, device_buffer_duration;
849 hr = IAudioClient_GetDevicePeriod (client, &default_period, &min_period);
850 HR_FAILED_RET (hr, IAudioClient::GetDevicePeriod, FALSE);
852 GST_INFO_OBJECT (self, "wasapi default period: %" G_GINT64_FORMAT
853 ", min period: %" G_GINT64_FORMAT, default_period, min_period);
855 rate = GST_AUDIO_INFO_RATE (&spec->info);
858 if (sharemode == AUDCLNT_SHAREMODE_SHARED) {
859 device_period = default_period;
860 device_buffer_duration = 0;
862 device_period = min_period;
863 device_buffer_duration = min_period;
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);
872 /* For some reason, we need to call this a second time for exclusive mode */
873 if (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE)
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);
881 if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED &&
882 sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
885 GST_WARNING_OBJECT (self, "initialize failed due to unaligned period %i",
886 (int) device_period);
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);
892 device_period = (GST_SECOND / 100) * n_frames / rate;
894 GST_WARNING_OBJECT (self, "trying to re-initialize with period %i "
895 "(%i frames, %i rate)", (int) device_period, n_frames, rate);
897 hr = IAudioClient_Initialize (client, sharemode,
898 AUDCLNT_STREAMFLAGS_EVENTCALLBACK, device_period,
899 device_period, format, NULL);
901 HR_FAILED_RET (hr, IAudioClient::Initialize, FALSE);
903 *ret_devicep_frames = (rate * device_period * 100) / GST_SECOND;
909 gst_wasapi_util_initialize_audioclient3 (GstElement * self,
910 GstAudioRingBufferSpec * spec, IAudioClient3 * client,
911 WAVEFORMATEX * format, gboolean low_latency, guint * ret_devicep_frames)
914 guint rate, devicep_frames;
915 guint defaultp_frames, fundp_frames, minp_frames, maxp_frames;
918 rate = GST_AUDIO_INFO_RATE (&spec->info);
920 hr = IAudioClient3_GetSharedModeEnginePeriod (client, format,
921 &defaultp_frames, &fundp_frames, &minp_frames, &maxp_frames);
922 HR_FAILED_RET (hr, IAudioClient3::GetSharedModeEnginePeriod, FALSE);
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);
929 devicep_frames = minp_frames;
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;
939 hr = IAudioClient3_InitializeSharedAudioStream (client,
940 AUDCLNT_STREAMFLAGS_EVENTCALLBACK, devicep_frames, format, NULL);
941 HR_FAILED_RET (hr, IAudioClient3::InitializeSharedAudioStream, FALSE);
943 hr = IAudioClient3_GetCurrentSharedModeEnginePeriod (client, &tmpf,
945 CoTaskMemFree (tmpf);
946 HR_FAILED_RET (hr, IAudioClient3::GetCurrentSharedModeEnginePeriod, FALSE);
948 *ret_devicep_frames = devicep_frames;
953 gst_wasapi_util_init_thread_priority (void)
955 if (gst_wasapi_avrt_tbl.tried_loading)
956 return gst_wasapi_avrt_tbl.dll != NULL;
958 if (!gst_wasapi_avrt_tbl.dll)
959 gst_wasapi_avrt_tbl.dll = LoadLibrary (TEXT ("avrt.dll"));
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;
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");
973 gst_wasapi_avrt_tbl.tried_loading = TRUE;
979 gst_wasapi_util_set_thread_characteristics (void)
983 if (!gst_wasapi_util_init_thread_priority ())
986 return gst_wasapi_avrt_tbl.AvSetMmThreadCharacteristics (TEXT ("Pro Audio"),
991 gst_wasapi_util_revert_thread_characteristics (HANDLE handle)
993 if (!gst_wasapi_util_init_thread_priority ())
996 gst_wasapi_avrt_tbl.AvRevertMmThreadCharacteristics (handle);