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