1 /// Translated from C to D 2 module soundio.wasapi; 3 4 extern(C): @nogc: nothrow: __gshared: 5 6 import soundio.soundio_internal; 7 import soundio.soundio_private; 8 import soundio.os; 9 import soundio.list; 10 import soundio.atomics; 11 import soundio.headers.wasapiheader; 12 import core.stdc.stdio; 13 import core.sys.windows.windows; 14 import core.stdc.stdlib: free; 15 import core.stdc..string: strlen; 16 import soundio.headers.wasapiheader; 17 import soundio.headers.wasapiheader: WAVEFORMATEX; 18 19 import core.atomic; // atomicOp!"+=" to replace InterlockedIncrement 20 21 // @nogc nothrow definitions 22 extern(Windows) { 23 void CoTaskMemFree(PVOID); 24 HRESULT CoCreateInstance(REFCLSID, LPUNKNOWN, DWORD, REFIID, PVOID*); 25 } 26 27 private: 28 29 /+ 30 import mmdeviceapi; 31 import audioclient; 32 import audiosessiontypes; 33 import audiopolicy; 34 version = INITGUID; 35 version = CINTERFACE; 36 version = COBJMACROS; 37 version = CONST_VTABLE; 38 import initguid; 39 import audioclient; 40 import endpointvolume; 41 import mmdeviceapi; 42 import mmreg; 43 import functiondiscoverykeys_devpkey; 44 import wasapi; 45 +/ 46 47 enum AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = 0x80000000; 48 enum AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY = 0x08000000; 49 50 package struct SoundIoDeviceWasapi { 51 double period_duration; 52 IMMDevice* mm_device; 53 } 54 55 package struct SoundIoWasapi { 56 SoundIoOsMutex* mutex; 57 SoundIoOsCond* cond; 58 SoundIoOsCond* scan_devices_cond; 59 SoundIoOsMutex* scan_devices_mutex; 60 SoundIoOsThread* thread; 61 bool abort_flag; 62 // this one is ready to be read with flush_events. protected by mutex 63 SoundIoDevicesInfo* ready_devices_info; 64 bool have_devices_flag; 65 bool device_scan_queued; 66 int shutdown_err; 67 bool emitted_shutdown_cb; 68 69 IMMDeviceEnumerator* device_enumerator; 70 IMMNotificationClient device_events; 71 LONG device_events_refs; 72 } 73 74 package struct SoundIoOutStreamWasapi { 75 IAudioClient* audio_client; 76 IAudioClockAdjustment* audio_clock_adjustment; 77 IAudioRenderClient* audio_render_client; 78 IAudioSessionControl* audio_session_control; 79 ISimpleAudioVolume* audio_volume_control; 80 LPWSTR stream_name; 81 bool need_resample; 82 SoundIoOsThread* thread; 83 SoundIoOsMutex* mutex; 84 SoundIoOsCond* cond; 85 SoundIoOsCond* start_cond; 86 SoundIoAtomicFlag thread_exit_flag; 87 bool is_raw; 88 int writable_frame_count; 89 UINT32 buffer_frame_count; 90 int write_frame_count; 91 HANDLE h_event; 92 SoundIoAtomicBool desired_pause_state; 93 SoundIoAtomicFlag pause_resume_flag; 94 SoundIoAtomicFlag clear_buffer_flag; 95 bool is_paused; 96 bool open_complete; 97 int open_err; 98 bool started; 99 UINT32 min_padding_frames; 100 float volume; 101 SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas; 102 } 103 104 package struct SoundIoInStreamWasapi { 105 IAudioClient* audio_client; 106 IAudioCaptureClient* audio_capture_client; 107 IAudioSessionControl* audio_session_control; 108 LPWSTR stream_name; 109 SoundIoOsThread* thread; 110 SoundIoOsMutex* mutex; 111 SoundIoOsCond* cond; 112 SoundIoOsCond* start_cond; 113 SoundIoAtomicFlag thread_exit_flag; 114 bool is_raw; 115 int readable_frame_count; 116 UINT32 buffer_frame_count; 117 int read_frame_count; 118 HANDLE h_event; 119 bool is_paused; 120 bool open_complete; 121 int open_err; 122 bool started; 123 char* read_buf; 124 int read_buf_frames_left; 125 int opened_buf_frames; 126 SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas; 127 } 128 129 enum E_NOTFOUND = 0x80070490; 130 131 /+ 132 // In C++ mode, IsEqualGUID() takes its arguments by reference 133 enum string IS_EQUAL_GUID(string a, string b) = ` IsEqualGUID(*(a), *(b))`; 134 enum string IS_EQUAL_IID(string a, string b) = ` IsEqualIID((a), *(b))`; 135 136 // And some constants are passed by reference 137 enum IID_IAUDIOCLIENT = (IID_IAudioClient); 138 enum IID_IMMENDPOINT = (IID_IMMEndpoint); 139 enum IID_IAUDIOCLOCKADJUSTMENT = (IID_IAudioClockAdjustment); 140 enum IID_IAUDIOSESSIONCONTROL = (IID_IAudioSessionControl); 141 enum IID_IAUDIORENDERCLIENT = (IID_IAudioRenderClient); 142 enum IID_IMMDEVICEENUMERATOR = (IID_IMMDeviceEnumerator); 143 enum IID_IAUDIOCAPTURECLIENT = (IID_IAudioCaptureClient); 144 enum IID_ISIMPLEAUDIOVOLUME = (IID_ISimpleAudioVolume); 145 enum CLSID_MMDEVICEENUMERATOR = (CLSID_MMDeviceEnumerator); 146 enum PKEY_DEVICE_FRIENDLYNAME = (PKEY_Device_FriendlyName); 147 enum PKEY_AUDIOENGINE_DEVICEFORMAT = (PKEY_AudioEngine_DeviceFormat); 148 +/ 149 150 // And some GUID are never implemented (Ignoring the INITGUID define) 151 static const(CLSID) CLSID_MMDeviceEnumerator = CLSID( 152 0xBCDE0395, 0xE52F, 0x467C, [0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E] 153 ); 154 // TODO: Verify this equals __uuidof(MMDeviceEnumerator); 155 // class DECLSPEC_UUID("BCDE0395-E52F-467C-8E3D-C4579291692E") 156 // MMDeviceEnumerator; 157 158 static const(IID) IID_IMMDeviceEnumerator = IID( 159 //MIDL_INTERFACE("A95664D2-9614-4F35-A746-DE8DB63617E6") 160 0xa95664d2, 0x9614, 0x4f35, [0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6] 161 ); 162 static const(IID) IID_IMMNotificationClient = IID( 163 //MIDL_INTERFACE("7991EEC9-7E89-4D85-8390-6C703CEC60C0") 164 0x7991eec9, 0x7e89, 0x4d85, [0x83, 0x90, 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0] 165 ); 166 static const(IID) IID_IAudioClient = IID( 167 //MIDL_INTERFACE("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") 168 0x1cb9ad4c, 0xdbfa, 0x4c32, [0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2] 169 ); 170 static const(IID) IID_IAudioRenderClient = IID( 171 //MIDL_INTERFACE("F294ACFC-3146-4483-A7BF-ADDCA7C260E2") 172 0xf294acfc, 0x3146, 0x4483, [0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2] 173 ); 174 static const(IID) IID_IAudioSessionControl = IID( 175 //MIDL_INTERFACE("F4B1A599-7266-4319-A8CA-E70ACB11E8CD") 176 0xf4b1a599, 0x7266, 0x4319, [0xa8, 0xca, 0xe7, 0x0a, 0xcb, 0x11, 0xe8, 0xcd] 177 ); 178 static const(IID) IID_IAudioSessionEvents = IID( 179 //MIDL_INTERFACE("24918ACC-64B3-37C1-8CA9-74A66E9957A8") 180 0x24918acc, 0x64b3, 0x37c1, [0x8c, 0xa9, 0x74, 0xa6, 0x6e, 0x99, 0x57, 0xa8] 181 ); 182 static const(IID) IID_IMMEndpoint = IID( 183 //MIDL_INTERFACE("1BE09788-6894-4089-8586-9A2A6C265AC5") 184 0x1be09788, 0x6894, 0x4089, [0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5] 185 ); 186 static const(IID) IID_IAudioClockAdjustment = IID( 187 //MIDL_INTERFACE("f6e4c0a0-46d9-4fb8-be21-57a3ef2b626c") 188 0xf6e4c0a0, 0x46d9, 0x4fb8, [0xbe, 0x21, 0x57, 0xa3, 0xef, 0x2b, 0x62, 0x6c] 189 ); 190 static const(IID) IID_IAudioCaptureClient = IID( 191 //MIDL_INTERFACE("C8ADBD64-E71E-48a0-A4DE-185C395CD317") 192 0xc8adbd64, 0xe71e, 0x48a0, [0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17] 193 ); 194 static const(IID) IID_ISimpleAudioVolume = IID( 195 //MIDL_INTERFACE("87ce5498-68d6-44e5-9215-6da47ef883d8") 196 0x87ce5498, 0x68d6, 0x44e5,[0x92, 0x15, 0x6d, 0xa4, 0x7e, 0xf8, 0x83, 0xd8 ] 197 ); 198 199 extern(Windows) BOOL IsEqualGUID(REFGUID rguid1, REFGUID rguid2); 200 alias IsEqualIID = IsEqualGUID; 201 bool IS_EQUAL_GUID(const(GUID)* a, const(GUID)* b) { return cast(bool) IsEqualGUID(a, b);} 202 bool IS_EQUAL_IID(const(IID)* a, const(IID)* b) { return cast(bool) IsEqualIID(a, b);} 203 204 enum IID_IAUDIOCLIENT = (&IID_IAudioClient); 205 enum IID_IMMENDPOINT = (&IID_IMMEndpoint); 206 enum PKEY_DEVICE_FRIENDLYNAME = (&PKEY_Device_FriendlyName); 207 enum PKEY_AUDIOENGINE_DEVICEFORMAT = (&PKEY_AudioEngine_DeviceFormat); 208 enum CLSID_MMDEVICEENUMERATOR = (&CLSID_MMDeviceEnumerator); 209 enum IID_IAUDIOCLOCKADJUSTMENT = (&IID_IAudioClockAdjustment); 210 enum IID_IAUDIOSESSIONCONTROL = (&IID_IAudioSessionControl); 211 enum IID_IAUDIORENDERCLIENT = (&IID_IAudioRenderClient); 212 enum IID_IMMDEVICEENUMERATOR = (&IID_IMMDeviceEnumerator); 213 enum IID_IAUDIOCAPTURECLIENT = (&IID_IAudioCaptureClient); 214 enum IID_ISIMPLEAUDIOVOLUME = (&IID_ISimpleAudioVolume); 215 216 // Attempting to use the Windows-supplied versions of these constants resulted 217 // in `undefined reference` linker errors. 218 static const(GUID) SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = GUID( 219 0x00000003,0x0000,0x0010, [0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); 220 221 static const(GUID) SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = GUID( 222 0x00000001,0x0000,0x0010, [0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); 223 224 // Adding more common sample rates helps the heuristics; feel free to do that. 225 static int[19] test_sample_rates = [ 226 8000, 227 11025, 228 16000, 229 22050, 230 32000, 231 37800, 232 44056, 233 44100, 234 47250, 235 48000, 236 50000, 237 50400, 238 88200, 239 96000, 240 176400, 241 192000, 242 352800, 243 2822400, 244 5644800, 245 ]; 246 247 // If you modify this list, also modify `to_wave_format_format` appropriately. 248 immutable SoundIoFormat[6] test_formats = [ 249 SoundIoFormat.U8, 250 SoundIoFormat.S16LE, 251 SoundIoFormat.S24LE, 252 SoundIoFormat.S32LE, 253 SoundIoFormat.Float32LE, 254 SoundIoFormat.Float64LE, 255 ]; 256 257 // If you modify this list, also modify `to_wave_format_layout` appropriately. 258 immutable SoundIoChannelLayoutId[7] test_layouts = [ 259 SoundIoChannelLayoutId.Mono, 260 SoundIoChannelLayoutId.Stereo, 261 SoundIoChannelLayoutId.Quad, 262 SoundIoChannelLayoutId._4Point0, 263 SoundIoChannelLayoutId._5Point1, 264 SoundIoChannelLayoutId._7Point1, 265 SoundIoChannelLayoutId._5Point1Back, 266 ]; 267 268 /* 269 // useful for debugging but no point in compiling into binary 270 static const char *hresult_to_str(HRESULT hr) { 271 switch (hr) { 272 default: return "(unknown)"; 273 case AUDCLNT_E_NOT_INITIALIZED: return "AUDCLNT_E_NOT_INITIALIZED"; 274 case AUDCLNT_E_ALREADY_INITIALIZED: return "AUDCLNT_E_ALREADY_INITIALIZED"; 275 case AUDCLNT_E_WRONG_ENDPOINT_TYPE: return "AUDCLNT_E_WRONG_ENDPOINT_TYPE"; 276 case AUDCLNT_E_DEVICE_INVALIDATED: return "AUDCLNT_E_DEVICE_INVALIDATED"; 277 case AUDCLNT_E_NOT_STOPPED: return "AUDCLNT_E_NOT_STOPPED"; 278 case AUDCLNT_E_BUFFER_TOO_LARGE: return "AUDCLNT_E_BUFFER_TOO_LARGE"; 279 case AUDCLNT_E_OUT_OF_ORDER: return "AUDCLNT_E_OUT_OF_ORDER"; 280 case AUDCLNT_E_UNSUPPORTED_FORMAT: return "AUDCLNT_E_UNSUPPORTED_FORMAT"; 281 case AUDCLNT_E_INVALID_SIZE: return "AUDCLNT_E_INVALID_SIZE"; 282 case AUDCLNT_E_DEVICE_IN_USE: return "AUDCLNT_E_DEVICE_IN_USE"; 283 case AUDCLNT_E_BUFFER_OPERATION_PENDING: return "AUDCLNT_E_BUFFER_OPERATION_PENDING"; 284 case AUDCLNT_E_THREAD_NOT_REGISTERED: return "AUDCLNT_E_THREAD_NOT_REGISTERED"; 285 case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: return "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED"; 286 case AUDCLNT_E_ENDPOINT_CREATE_FAILED: return "AUDCLNT_E_ENDPOINT_CREATE_FAILED"; 287 case AUDCLNT_E_SERVICE_NOT_RUNNING: return "AUDCLNT_E_SERVICE_NOT_RUNNING"; 288 case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: return "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED"; 289 case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: return "AUDCLNT_E_EXCLUSIVE_MODE_ONLY"; 290 case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: return "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"; 291 case AUDCLNT_E_EVENTHANDLE_NOT_SET: return "AUDCLNT_E_EVENTHANDLE_NOT_SET"; 292 case AUDCLNT_E_INCORRECT_BUFFER_SIZE: return "AUDCLNT_E_INCORRECT_BUFFER_SIZE"; 293 case AUDCLNT_E_BUFFER_SIZE_ERROR: return "AUDCLNT_E_BUFFER_SIZE_ERROR"; 294 case AUDCLNT_E_CPUUSAGE_EXCEEDED: return "AUDCLNT_E_CPUUSAGE_EXCEEDED"; 295 case AUDCLNT_E_BUFFER_ERROR: return "AUDCLNT_E_BUFFER_ERROR"; 296 case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: return "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED"; 297 case AUDCLNT_E_INVALID_DEVICE_PERIOD: return "AUDCLNT_E_INVALID_DEVICE_PERIOD"; 298 case AUDCLNT_E_INVALID_STREAM_FLAG: return "AUDCLNT_E_INVALID_STREAM_FLAG"; 299 case AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE: return "AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE"; 300 case AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES: return "AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES"; 301 case AUDCLNT_E_OFFLOAD_MODE_ONLY: return "AUDCLNT_E_OFFLOAD_MODE_ONLY"; 302 case AUDCLNT_E_NONOFFLOAD_MODE_ONLY: return "AUDCLNT_E_NONOFFLOAD_MODE_ONLY"; 303 case AUDCLNT_E_RESOURCES_INVALIDATED: return "AUDCLNT_E_RESOURCES_INVALIDATED"; 304 case AUDCLNT_S_BUFFER_EMPTY: return "AUDCLNT_S_BUFFER_EMPTY"; 305 case AUDCLNT_S_THREAD_ALREADY_REGISTERED: return "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; 306 case AUDCLNT_S_POSITION_STALLED: return "AUDCLNT_S_POSITION_STALLED"; 307 308 case E_POINTER: return "E_POINTER"; 309 case E_INVALIDARG: return "E_INVALIDARG"; 310 case E_OUTOFMEMORY: return "E_OUTOFMEMORY"; 311 } 312 } 313 */ 314 315 // converts a windows wide string to a UTF-8 encoded char * 316 // Possible errors: 317 // * SoundIoError.oMem 318 // * SoundIoError.EncodingString 319 static int from_lpwstr(LPWSTR lpwstr, char** out_str, int* out_str_len) { 320 DWORD flags = 0; 321 int buf_size = WideCharToMultiByte(CP_UTF8, flags, lpwstr, -1, null, 0, null, null); 322 323 if (buf_size == 0) 324 return SoundIoError.EncodingString; 325 326 char* buf = ALLOCATE!char(buf_size); 327 if (!buf) 328 return SoundIoError.NoMem; 329 330 if (WideCharToMultiByte(CP_UTF8, flags, lpwstr, -1, buf, buf_size, null, null) != buf_size) { 331 free(buf); 332 return SoundIoError.EncodingString; 333 } 334 335 *out_str = buf; 336 *out_str_len = buf_size - 1; 337 338 return 0; 339 } 340 341 static int to_lpwstr(const(char)* str, int str_len, LPWSTR* out_lpwstr) { 342 DWORD flags = 0; 343 int w_len = MultiByteToWideChar(CP_UTF8, flags, str, str_len, null, 0); 344 if (w_len <= 0) 345 return SoundIoError.EncodingString; 346 347 LPWSTR buf = ALLOCATE!wchar(w_len + 1); 348 if (!buf) 349 return SoundIoError.NoMem; 350 351 if (MultiByteToWideChar(CP_UTF8, flags, str, str_len, buf, w_len) != w_len) { 352 free(buf); 353 return SoundIoError.EncodingString; 354 } 355 356 *out_lpwstr = buf; 357 return 0; 358 } 359 360 static void from_channel_mask_layout(UINT channel_mask, SoundIoChannelLayout* layout) { 361 layout.channel_count = 0; 362 if (channel_mask & SPEAKER_FRONT_LEFT) 363 layout.channels[layout.channel_count++] = SoundIoChannelId.FrontLeft; 364 if (channel_mask & SPEAKER_FRONT_RIGHT) 365 layout.channels[layout.channel_count++] = SoundIoChannelId.FrontRight; 366 if (channel_mask & SPEAKER_FRONT_CENTER) 367 layout.channels[layout.channel_count++] = SoundIoChannelId.FrontCenter; 368 if (channel_mask & SPEAKER_LOW_FREQUENCY) 369 layout.channels[layout.channel_count++] = SoundIoChannelId.Lfe; 370 if (channel_mask & SPEAKER_BACK_LEFT) 371 layout.channels[layout.channel_count++] = SoundIoChannelId.BackLeft; 372 if (channel_mask & SPEAKER_BACK_RIGHT) 373 layout.channels[layout.channel_count++] = SoundIoChannelId.BackRight; 374 if (channel_mask & SPEAKER_FRONT_LEFT_OF_CENTER) 375 layout.channels[layout.channel_count++] = SoundIoChannelId.FrontLeftCenter; 376 if (channel_mask & SPEAKER_FRONT_RIGHT_OF_CENTER) 377 layout.channels[layout.channel_count++] = SoundIoChannelId.FrontRightCenter; 378 if (channel_mask & SPEAKER_BACK_CENTER) 379 layout.channels[layout.channel_count++] = SoundIoChannelId.BackCenter; 380 if (channel_mask & SPEAKER_SIDE_LEFT) 381 layout.channels[layout.channel_count++] = SoundIoChannelId.SideLeft; 382 if (channel_mask & SPEAKER_SIDE_RIGHT) 383 layout.channels[layout.channel_count++] = SoundIoChannelId.SideRight; 384 if (channel_mask & SPEAKER_TOP_CENTER) 385 layout.channels[layout.channel_count++] = SoundIoChannelId.TopCenter; 386 if (channel_mask & SPEAKER_TOP_FRONT_LEFT) 387 layout.channels[layout.channel_count++] = SoundIoChannelId.TopFrontLeft; 388 if (channel_mask & SPEAKER_TOP_FRONT_CENTER) 389 layout.channels[layout.channel_count++] = SoundIoChannelId.TopFrontCenter; 390 if (channel_mask & SPEAKER_TOP_FRONT_RIGHT) 391 layout.channels[layout.channel_count++] = SoundIoChannelId.TopFrontRight; 392 if (channel_mask & SPEAKER_TOP_BACK_LEFT) 393 layout.channels[layout.channel_count++] = SoundIoChannelId.TopBackLeft; 394 if (channel_mask & SPEAKER_TOP_BACK_CENTER) 395 layout.channels[layout.channel_count++] = SoundIoChannelId.TopBackCenter; 396 if (channel_mask & SPEAKER_TOP_BACK_RIGHT) 397 layout.channels[layout.channel_count++] = SoundIoChannelId.TopBackRight; 398 399 soundio_channel_layout_detect_builtin(layout); 400 } 401 402 static void from_wave_format_layout(WAVEFORMATEXTENSIBLE* wave_format, SoundIoChannelLayout* layout) { 403 assert(wave_format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE); 404 layout.channel_count = 0; 405 from_channel_mask_layout(wave_format.dwChannelMask, layout); 406 } 407 408 static SoundIoFormat from_wave_format_format(WAVEFORMATEXTENSIBLE* wave_format) { 409 assert(wave_format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE); 410 bool is_pcm = IS_EQUAL_GUID(&wave_format.SubFormat, &SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM); 411 bool is_float = IS_EQUAL_GUID(&wave_format.SubFormat, &SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT); 412 413 if (wave_format.Samples.wValidBitsPerSample == wave_format.Format.wBitsPerSample) { 414 if (wave_format.Format.wBitsPerSample == 8) { 415 if (is_pcm) 416 return SoundIoFormat.U8; 417 } else if (wave_format.Format.wBitsPerSample == 16) { 418 if (is_pcm) 419 return SoundIoFormat.S16LE; 420 } else if (wave_format.Format.wBitsPerSample == 32) { 421 if (is_pcm) 422 return SoundIoFormat.S32LE; 423 else if (is_float) 424 return SoundIoFormat.Float32LE; 425 } else if (wave_format.Format.wBitsPerSample == 64) { 426 if (is_float) 427 return SoundIoFormat.Float64LE; 428 } 429 } else if (wave_format.Format.wBitsPerSample == 32 && 430 wave_format.Samples.wValidBitsPerSample == 24) 431 { 432 return SoundIoFormat.S24LE; 433 } 434 435 return SoundIoFormat.Invalid; 436 } 437 438 // only needs to support the layouts in test_layouts 439 static void to_wave_format_layout(const(SoundIoChannelLayout)* layout, WAVEFORMATEXTENSIBLE* wave_format) { 440 wave_format.dwChannelMask = 0; 441 wave_format.Format.nChannels = cast(ushort) layout.channel_count; 442 for (int i = 0; i < layout.channel_count; i += 1) { 443 SoundIoChannelId channel_id = layout.channels[i]; 444 switch (channel_id) { 445 case SoundIoChannelId.FrontLeft: 446 wave_format.dwChannelMask |= SPEAKER_FRONT_LEFT; 447 break; 448 case SoundIoChannelId.FrontRight: 449 wave_format.dwChannelMask |= SPEAKER_FRONT_RIGHT; 450 break; 451 case SoundIoChannelId.FrontCenter: 452 wave_format.dwChannelMask |= SPEAKER_FRONT_CENTER; 453 break; 454 case SoundIoChannelId.Lfe: 455 wave_format.dwChannelMask |= SPEAKER_LOW_FREQUENCY; 456 break; 457 case SoundIoChannelId.BackLeft: 458 wave_format.dwChannelMask |= SPEAKER_BACK_LEFT; 459 break; 460 case SoundIoChannelId.BackRight: 461 wave_format.dwChannelMask |= SPEAKER_BACK_RIGHT; 462 break; 463 case SoundIoChannelId.FrontLeftCenter: 464 wave_format.dwChannelMask |= SPEAKER_FRONT_LEFT_OF_CENTER; 465 break; 466 case SoundIoChannelId.FrontRightCenter: 467 wave_format.dwChannelMask |= SPEAKER_FRONT_RIGHT_OF_CENTER; 468 break; 469 case SoundIoChannelId.BackCenter: 470 wave_format.dwChannelMask |= SPEAKER_BACK_CENTER; 471 break; 472 case SoundIoChannelId.SideLeft: 473 wave_format.dwChannelMask |= SPEAKER_SIDE_LEFT; 474 break; 475 case SoundIoChannelId.SideRight: 476 wave_format.dwChannelMask |= SPEAKER_SIDE_RIGHT; 477 break; 478 case SoundIoChannelId.TopCenter: 479 wave_format.dwChannelMask |= SPEAKER_TOP_CENTER; 480 break; 481 case SoundIoChannelId.TopFrontLeft: 482 wave_format.dwChannelMask |= SPEAKER_TOP_FRONT_LEFT; 483 break; 484 case SoundIoChannelId.TopFrontCenter: 485 wave_format.dwChannelMask |= SPEAKER_TOP_FRONT_CENTER; 486 break; 487 case SoundIoChannelId.TopFrontRight: 488 wave_format.dwChannelMask |= SPEAKER_TOP_FRONT_RIGHT; 489 break; 490 case SoundIoChannelId.TopBackLeft: 491 wave_format.dwChannelMask |= SPEAKER_TOP_BACK_LEFT; 492 break; 493 case SoundIoChannelId.TopBackCenter: 494 wave_format.dwChannelMask |= SPEAKER_TOP_BACK_CENTER; 495 break; 496 case SoundIoChannelId.TopBackRight: 497 wave_format.dwChannelMask |= SPEAKER_TOP_BACK_RIGHT; 498 break; 499 default: 500 soundio_panic("to_wave_format_layout: unsupported channel id"); 501 } 502 } 503 } 504 505 // only needs to support the formats in test_formats 506 static void to_wave_format_format(SoundIoFormat format, WAVEFORMATEXTENSIBLE* wave_format) { 507 switch (format) { 508 case SoundIoFormat.U8: 509 wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM; 510 wave_format.Format.wBitsPerSample = 8; 511 wave_format.Samples.wValidBitsPerSample = 8; 512 break; 513 case SoundIoFormat.S16LE: 514 wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM; 515 wave_format.Format.wBitsPerSample = 16; 516 wave_format.Samples.wValidBitsPerSample = 16; 517 break; 518 case SoundIoFormat.S24LE: 519 wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM; 520 wave_format.Format.wBitsPerSample = 32; 521 wave_format.Samples.wValidBitsPerSample = 24; 522 break; 523 case SoundIoFormat.S32LE: 524 wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM; 525 wave_format.Format.wBitsPerSample = 32; 526 wave_format.Samples.wValidBitsPerSample = 32; 527 break; 528 case SoundIoFormat.Float32LE: 529 wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; 530 wave_format.Format.wBitsPerSample = 32; 531 wave_format.Samples.wValidBitsPerSample = 32; 532 break; 533 case SoundIoFormat.Float64LE: 534 wave_format.SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; 535 wave_format.Format.wBitsPerSample = 64; 536 wave_format.Samples.wValidBitsPerSample = 64; 537 break; 538 default: 539 soundio_panic("to_wave_format_format: unsupported format"); 540 } 541 } 542 543 static void complete_wave_format_data(WAVEFORMATEXTENSIBLE* wave_format) { 544 wave_format.Format.nBlockAlign = cast(ushort) ((wave_format.Format.wBitsPerSample * wave_format.Format.nChannels) / 8); 545 wave_format.Format.nAvgBytesPerSec = wave_format.Format.nSamplesPerSec * wave_format.Format.nBlockAlign; 546 } 547 548 static SoundIoDeviceAim data_flow_to_aim(EDataFlow data_flow) { 549 return (data_flow == eRender) ? SoundIoDeviceAim.Output : SoundIoDeviceAim.Input; 550 } 551 552 553 static double from_reference_time(REFERENCE_TIME rt) { 554 return (cast(double)rt) / 10000000.0; 555 } 556 557 static REFERENCE_TIME to_reference_time(double seconds) { 558 return cast(REFERENCE_TIME)(seconds * 10000000.0 + 0.5); 559 } 560 561 static void destruct_device(SoundIoDevicePrivate* dev) { 562 SoundIoDeviceWasapi* dw = &dev.backend_data.wasapi; 563 if (dw.mm_device) 564 IMMDevice_Release(dw.mm_device); 565 } 566 567 struct RefreshDevices { 568 IMMDeviceCollection* collection; 569 IMMDevice* mm_device; 570 IMMDevice* default_render_device; 571 IMMDevice* default_capture_device; 572 IMMEndpoint* endpoint; 573 IPropertyStore* prop_store; 574 IAudioClient* audio_client; 575 LPWSTR lpwstr; 576 PROPVARIANT prop_variant_value; 577 WAVEFORMATEXTENSIBLE* wave_format; 578 bool prop_variant_value_inited; 579 SoundIoDevicesInfo* devices_info; 580 SoundIoDevice* device_shared; 581 SoundIoDevice* device_raw; 582 char* default_render_id; 583 int default_render_id_len; 584 char* default_capture_id; 585 int default_capture_id_len; 586 } 587 // static assert(RefreshDevices.sizeof == 160); 64-bit 588 589 static void deinit_refresh_devices(RefreshDevices* rd) { 590 soundio_destroy_devices_info(rd.devices_info); 591 soundio_device_unref(rd.device_shared); 592 soundio_device_unref(rd.device_raw); 593 if (rd.mm_device) 594 IMMDevice_Release(rd.mm_device); 595 if (rd.default_render_device) 596 { 597 IMMDevice_Release(rd.default_render_device); 598 free(rd.default_render_id); 599 } 600 if (rd.default_capture_device) 601 { 602 IMMDevice_Release(rd.default_capture_device); 603 free(rd.default_capture_id); 604 } 605 if (rd.collection) 606 IMMDeviceCollection_Release(rd.collection); 607 if (rd.lpwstr) 608 CoTaskMemFree(rd.lpwstr); 609 if (rd.endpoint) 610 IMMEndpoint_Release(rd.endpoint); 611 if (rd.prop_store) 612 IPropertyStore_Release(rd.prop_store); 613 if (rd.prop_variant_value_inited) 614 PropVariantClear(&rd.prop_variant_value); 615 if (rd.wave_format) 616 CoTaskMemFree(rd.wave_format); 617 if (rd.audio_client) 618 IUnknown_Release(rd.audio_client); 619 } 620 621 static int detect_valid_layouts(RefreshDevices* rd, WAVEFORMATEXTENSIBLE* wave_format, SoundIoDevicePrivate* dev, AUDCLNT_SHAREMODE share_mode) { 622 SoundIoDevice* device = &dev.pub; 623 HRESULT hr; 624 625 device.layout_count = 0; 626 device.layouts = ALLOCATE!SoundIoChannelLayout(test_layouts.length); 627 if (!device.layouts) 628 return SoundIoError.NoMem; 629 630 WAVEFORMATEX* closest_match = null; 631 WAVEFORMATEXTENSIBLE orig_wave_format = *wave_format; 632 633 for (int i = 0; i < test_formats.length; i += 1) { 634 SoundIoChannelLayoutId test_layout_id = test_layouts[i]; 635 const(SoundIoChannelLayout)* test_layout = soundio_channel_layout_get_builtin(test_layout_id); 636 to_wave_format_layout(test_layout, wave_format); 637 complete_wave_format_data(wave_format); 638 639 hr = IAudioClient_IsFormatSupported(rd.audio_client, share_mode, 640 cast(WAVEFORMATEX*)wave_format, &closest_match); 641 if (closest_match) { 642 CoTaskMemFree(closest_match); 643 closest_match = null; 644 } 645 if (hr == S_OK) { 646 device.layouts[device.layout_count++] = *test_layout; 647 } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) { 648 continue; 649 } else { 650 *wave_format = orig_wave_format; 651 return SoundIoError.OpeningDevice; 652 } 653 } 654 655 *wave_format = orig_wave_format; 656 return 0; 657 } 658 659 static int detect_valid_formats(RefreshDevices* rd, WAVEFORMATEXTENSIBLE* wave_format, SoundIoDevicePrivate* dev, AUDCLNT_SHAREMODE share_mode) { 660 SoundIoDevice* device = &dev.pub; 661 HRESULT hr; 662 663 device.format_count = 0; 664 device.formats = ALLOCATE!SoundIoFormat(test_formats.length); 665 if (!device.formats) 666 return SoundIoError.NoMem; 667 668 WAVEFORMATEX* closest_match = null; 669 WAVEFORMATEXTENSIBLE orig_wave_format = *wave_format; 670 671 for (int i = 0; i < test_formats.length; i += 1) { 672 SoundIoFormat test_format = test_formats[i]; 673 to_wave_format_format(test_format, wave_format); 674 complete_wave_format_data(wave_format); 675 676 hr = IAudioClient_IsFormatSupported(rd.audio_client, share_mode, 677 cast(WAVEFORMATEX*)wave_format, &closest_match); 678 if (closest_match) { 679 CoTaskMemFree(closest_match); 680 closest_match = null; 681 } 682 if (hr == S_OK) { 683 device.formats[device.format_count++] = test_format; 684 } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) { 685 continue; 686 } else { 687 *wave_format = orig_wave_format; 688 return SoundIoError.OpeningDevice; 689 } 690 } 691 692 *wave_format = orig_wave_format; 693 return 0; 694 } 695 696 static int add_sample_rate(SoundIoListSampleRateRange* sample_rates, int* current_min, int the_max) { 697 if (auto err = sample_rates.add_one()) 698 return err; 699 700 SoundIoSampleRateRange* last_range = sample_rates.last_ptr(); 701 last_range.min = *current_min; 702 last_range.max = the_max; 703 return 0; 704 } 705 706 static int do_sample_rate_test(RefreshDevices* rd, SoundIoDevicePrivate* dev, WAVEFORMATEXTENSIBLE* wave_format, int test_sample_rate, AUDCLNT_SHAREMODE share_mode, int* current_min, int* last_success_rate) { 707 WAVEFORMATEX* closest_match = null; 708 709 wave_format.Format.nSamplesPerSec = test_sample_rate; 710 HRESULT hr = IAudioClient_IsFormatSupported(rd.audio_client, share_mode, 711 cast(WAVEFORMATEX*)wave_format, &closest_match); 712 if (closest_match) { 713 CoTaskMemFree(closest_match); 714 closest_match = null; 715 } 716 if (hr == S_OK) { 717 if (*current_min == -1) { 718 *current_min = test_sample_rate; 719 } 720 *last_success_rate = test_sample_rate; 721 } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) { 722 if (*current_min != -1) { 723 if (auto err = add_sample_rate(&dev.sample_rates, current_min, *last_success_rate)) 724 return err; 725 *current_min = -1; 726 } 727 } else { 728 return SoundIoError.OpeningDevice; 729 } 730 731 return 0; 732 } 733 734 static int detect_valid_sample_rates(RefreshDevices* rd, WAVEFORMATEXTENSIBLE* wave_format, SoundIoDevicePrivate* dev, AUDCLNT_SHAREMODE share_mode) { 735 DWORD orig_sample_rate = wave_format.Format.nSamplesPerSec; 736 737 assert(dev.sample_rates.length == 0); 738 739 int current_min = -1; 740 int last_success_rate = -1; 741 for (int i = 0; i < test_sample_rates.length; i += 1) { 742 for (int offset = -1; offset <= 1; offset += 1) { 743 int test_sample_rate = test_sample_rates[i] + offset; 744 if (auto err = do_sample_rate_test(rd, dev, wave_format, test_sample_rate, share_mode, 745 ¤t_min, &last_success_rate)) 746 { 747 wave_format.Format.nSamplesPerSec = orig_sample_rate; 748 return err; 749 } 750 } 751 } 752 753 if (current_min != -1) { 754 if (auto err = add_sample_rate(&dev.sample_rates, ¤t_min, last_success_rate)) { 755 wave_format.Format.nSamplesPerSec = orig_sample_rate; 756 return err; 757 } 758 } 759 760 SoundIoDevice* device = &dev.pub; 761 762 device.sample_rate_count = dev.sample_rates.length; 763 device.sample_rates = dev.sample_rates.items; 764 765 wave_format.Format.nSamplesPerSec = orig_sample_rate; 766 return 0; 767 } 768 769 770 static int refresh_devices(SoundIoPrivate* si) { 771 SoundIo* soundio = &si.pub; 772 SoundIoWasapi* siw = &si.backend_data.wasapi; 773 RefreshDevices rd = RefreshDevices.init; // todo: is this zero? 774 HRESULT hr; 775 776 if (FAILED(hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(siw.device_enumerator, eRender, 777 eMultimedia, &rd.default_render_device))) 778 { 779 if(hr != E_NOTFOUND) { 780 deinit_refresh_devices(&rd); 781 if(hr == E_OUTOFMEMORY) { 782 return SoundIoError.NoMem; 783 } 784 return SoundIoError.OpeningDevice; 785 } 786 } 787 if(rd.default_render_device) { 788 if (rd.lpwstr) { 789 CoTaskMemFree(rd.lpwstr); 790 rd.lpwstr = null; 791 } 792 if (FAILED(hr = IMMDevice_GetId(rd.default_render_device, &rd.lpwstr))) { 793 deinit_refresh_devices(&rd); 794 // MSDN states the IMMDevice_GetId can fail if the device is NULL, or if we're out of memory 795 // We know the device point isn't NULL so we're necessarily out of memory 796 return SoundIoError.NoMem; 797 } 798 if (auto err = from_lpwstr(rd.lpwstr, &rd.default_render_id, &rd.default_render_id_len)) { 799 deinit_refresh_devices(&rd); 800 return err; 801 } 802 } 803 804 805 if (FAILED(hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(siw.device_enumerator, eCapture, 806 eMultimedia, &rd.default_capture_device))) 807 { 808 if(hr != E_NOTFOUND) { 809 deinit_refresh_devices(&rd); 810 if(hr == E_OUTOFMEMORY) { 811 return SoundIoError.NoMem; 812 } 813 return SoundIoError.OpeningDevice; 814 } 815 } 816 if(rd.default_capture_device) { 817 if (rd.lpwstr) { 818 CoTaskMemFree(rd.lpwstr); 819 rd.lpwstr = null; 820 } 821 if (FAILED(hr = IMMDevice_GetId(rd.default_capture_device, &rd.lpwstr))) { 822 deinit_refresh_devices(&rd); 823 if(hr == E_OUTOFMEMORY) { 824 return SoundIoError.NoMem; 825 } 826 return SoundIoError.OpeningDevice; 827 } 828 if (auto err = from_lpwstr(rd.lpwstr, &rd.default_capture_id, &rd.default_capture_id_len)) { 829 deinit_refresh_devices(&rd); 830 return err; 831 } 832 } 833 834 835 if (FAILED(hr = IMMDeviceEnumerator_EnumAudioEndpoints(siw.device_enumerator, 836 eAll, DEVICE_STATE_ACTIVE, &rd.collection))) 837 { 838 deinit_refresh_devices(&rd); 839 if(hr == E_OUTOFMEMORY) { 840 return SoundIoError.NoMem; 841 } 842 return SoundIoError.OpeningDevice; 843 } 844 845 UINT unsigned_count; 846 if (FAILED(hr = IMMDeviceCollection_GetCount(rd.collection, &unsigned_count))) { 847 // In theory this shouldn't happen since the only documented failure case is that 848 // rd.collection is NULL, but then EnumAudioEndpoints should have failed. 849 deinit_refresh_devices(&rd); 850 return SoundIoError.OpeningDevice; 851 } 852 853 if (unsigned_count > cast(UINT)int.max) { 854 deinit_refresh_devices(&rd); 855 return SoundIoError.IncompatibleDevice; 856 } 857 858 int device_count = unsigned_count; 859 860 if (!cast(bool)(rd.devices_info = ALLOCATE!SoundIoDevicesInfo(1))) { 861 deinit_refresh_devices(&rd); 862 return SoundIoError.NoMem; 863 } 864 rd.devices_info.default_input_index = -1; 865 rd.devices_info.default_output_index = -1; 866 867 for (int device_i = 0; device_i < device_count; device_i += 1) { 868 if (rd.mm_device) { 869 IMMDevice_Release(rd.mm_device); 870 rd.mm_device = null; 871 } 872 if (FAILED(hr = IMMDeviceCollection_Item(rd.collection, device_i, &rd.mm_device))) { 873 continue; 874 } 875 if (rd.lpwstr) { 876 CoTaskMemFree(rd.lpwstr); 877 rd.lpwstr = null; 878 } 879 if (FAILED(hr = IMMDevice_GetId(rd.mm_device, &rd.lpwstr))) { 880 continue; 881 } 882 883 884 885 SoundIoDevicePrivate* dev_shared = ALLOCATE!SoundIoDevicePrivate(1); 886 if (!dev_shared) { 887 deinit_refresh_devices(&rd); 888 return SoundIoError.NoMem; 889 } 890 SoundIoDeviceWasapi* dev_w_shared = &dev_shared.backend_data.wasapi; 891 dev_shared.destruct = &destruct_device; 892 assert(!rd.device_shared); 893 rd.device_shared = &dev_shared.pub; 894 rd.device_shared.ref_count = 1; 895 rd.device_shared.soundio = soundio; 896 rd.device_shared.is_raw = false; 897 rd.device_shared.software_latency_max = 2.0; 898 899 SoundIoDevicePrivate* dev_raw = ALLOCATE!SoundIoDevicePrivate(1); 900 if (!dev_raw) { 901 deinit_refresh_devices(&rd); 902 return SoundIoError.NoMem; 903 } 904 SoundIoDeviceWasapi* dev_w_raw = &dev_raw.backend_data.wasapi; 905 dev_raw.destruct = &destruct_device; 906 assert(!rd.device_raw); 907 rd.device_raw = &dev_raw.pub; 908 rd.device_raw.ref_count = 1; 909 rd.device_raw.soundio = soundio; 910 rd.device_raw.is_raw = true; 911 rd.device_raw.software_latency_max = 0.5; 912 913 int device_id_len; 914 if (auto err = from_lpwstr(rd.lpwstr, &rd.device_shared.id, &device_id_len)) { 915 deinit_refresh_devices(&rd); 916 return err; 917 } 918 919 rd.device_raw.id = soundio_str_dupe(rd.device_shared.id, device_id_len); 920 if (!rd.device_raw.id) { 921 deinit_refresh_devices(&rd); 922 return SoundIoError.NoMem; 923 } 924 925 if (rd.endpoint) { 926 IMMEndpoint_Release(rd.endpoint); 927 rd.endpoint = null; 928 } 929 if (FAILED(hr = IMMDevice_QueryInterface(rd.mm_device, IID_IMMENDPOINT, cast(void**)&rd.endpoint))) { 930 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 931 rd.device_raw.probe_error = SoundIoError.OpeningDevice; 932 rd.device_shared = null; 933 rd.device_raw = null; 934 continue; 935 } 936 937 EDataFlow data_flow; 938 if (FAILED(hr = IMMEndpoint_GetDataFlow(rd.endpoint, &data_flow))) { 939 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 940 rd.device_raw.probe_error = SoundIoError.OpeningDevice; 941 rd.device_shared = null; 942 rd.device_raw = null; 943 continue; 944 } 945 946 rd.device_shared.aim = data_flow_to_aim(data_flow); 947 rd.device_raw.aim = rd.device_shared.aim; 948 949 SoundIoListDevicePtr* device_list; 950 if (rd.device_shared.aim == SoundIoDeviceAim.Output) { 951 device_list = &rd.devices_info.output_devices; 952 if (soundio_streql(rd.device_shared.id, device_id_len, 953 rd.default_render_id, rd.default_render_id_len)) 954 { 955 rd.devices_info.default_output_index = device_list.length; 956 } 957 } else { 958 assert(rd.device_shared.aim == SoundIoDeviceAim.Input); 959 device_list = &rd.devices_info.input_devices; 960 if (soundio_streql(rd.device_shared.id, device_id_len, 961 rd.default_capture_id, rd.default_capture_id_len)) 962 { 963 rd.devices_info.default_input_index = device_list.length; 964 } 965 } 966 967 if (auto err = device_list.append(rd.device_shared)) { 968 deinit_refresh_devices(&rd); 969 return err; 970 } 971 if (auto err = device_list.append(rd.device_raw)) { 972 deinit_refresh_devices(&rd); 973 return err; 974 } 975 976 if (rd.audio_client) { 977 IUnknown_Release(rd.audio_client); 978 rd.audio_client = null; 979 } 980 if (FAILED(hr = IMMDevice_Activate(rd.mm_device, IID_IAUDIOCLIENT, 981 CLSCTX_ALL, null, cast(void**)&rd.audio_client))) 982 { 983 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 984 rd.device_raw.probe_error = SoundIoError.OpeningDevice; 985 rd.device_shared = null; 986 rd.device_raw = null; 987 continue; 988 } 989 990 REFERENCE_TIME default_device_period; 991 REFERENCE_TIME min_device_period; 992 if (FAILED(hr = IAudioClient_GetDevicePeriod(rd.audio_client, 993 &default_device_period, &min_device_period))) 994 { 995 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 996 rd.device_raw.probe_error = SoundIoError.OpeningDevice; 997 rd.device_shared = null; 998 rd.device_raw = null; 999 continue; 1000 } 1001 dev_w_shared.period_duration = from_reference_time(default_device_period); 1002 rd.device_shared.software_latency_current = dev_w_shared.period_duration; 1003 1004 dev_w_raw.period_duration = from_reference_time(min_device_period); 1005 rd.device_raw.software_latency_min = dev_w_raw.period_duration * 2; 1006 1007 if (rd.prop_store) { 1008 IPropertyStore_Release(rd.prop_store); 1009 rd.prop_store = null; 1010 } 1011 if (FAILED(hr = IMMDevice_OpenPropertyStore(rd.mm_device, STGM_READ, &rd.prop_store))) { 1012 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 1013 rd.device_raw.probe_error = SoundIoError.OpeningDevice; 1014 rd.device_shared = null; 1015 rd.device_raw = null; 1016 continue; 1017 } 1018 1019 if (rd.prop_variant_value_inited) { 1020 PropVariantClear(&rd.prop_variant_value); 1021 rd.prop_variant_value_inited = false; 1022 } 1023 PropVariantInit(&rd.prop_variant_value); 1024 rd.prop_variant_value_inited = true; 1025 if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store, 1026 PKEY_DEVICE_FRIENDLYNAME, &rd.prop_variant_value))) 1027 { 1028 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 1029 rd.device_raw.probe_error = SoundIoError.OpeningDevice; 1030 rd.device_shared = null; 1031 rd.device_raw = null; 1032 continue; 1033 } 1034 if (!rd.prop_variant_value.pwszVal) { 1035 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 1036 rd.device_raw.probe_error = SoundIoError.OpeningDevice; 1037 rd.device_shared = null; 1038 rd.device_raw = null; 1039 continue; 1040 } 1041 int device_name_len; 1042 if (auto err = from_lpwstr(rd.prop_variant_value.pwszVal, &rd.device_shared.name, &device_name_len)) { 1043 rd.device_shared.probe_error = err; 1044 rd.device_raw.probe_error = err; 1045 rd.device_shared = null; 1046 rd.device_raw = null; 1047 continue; 1048 } 1049 1050 rd.device_raw.name = soundio_str_dupe(rd.device_shared.name, device_name_len); 1051 if (!rd.device_raw.name) { 1052 deinit_refresh_devices(&rd); 1053 return SoundIoError.NoMem; 1054 } 1055 1056 // Get the format that WASAPI opens the device with for shared streams. 1057 // This is guaranteed to work, so we use this to modulate the sample 1058 // rate while holding the format constant and vice versa. 1059 if (rd.prop_variant_value_inited) { 1060 PropVariantClear(&rd.prop_variant_value); 1061 rd.prop_variant_value_inited = false; 1062 } 1063 PropVariantInit(&rd.prop_variant_value); 1064 rd.prop_variant_value_inited = true; 1065 if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store, PKEY_AUDIOENGINE_DEVICEFORMAT, 1066 &rd.prop_variant_value))) 1067 { 1068 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 1069 rd.device_raw.probe_error = SoundIoError.OpeningDevice; 1070 rd.device_shared = null; 1071 rd.device_raw = null; 1072 continue; 1073 } 1074 WAVEFORMATEXTENSIBLE* valid_wave_format = cast(WAVEFORMATEXTENSIBLE*)rd.prop_variant_value.blob.pBlobData; 1075 if (valid_wave_format.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) { 1076 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 1077 rd.device_raw.probe_error = SoundIoError.OpeningDevice; 1078 rd.device_shared = null; 1079 rd.device_raw = null; 1080 continue; 1081 } 1082 if (auto err = detect_valid_sample_rates(&rd, valid_wave_format, dev_raw, 1083 AUDCLNT_SHAREMODE_EXCLUSIVE)) 1084 { 1085 rd.device_raw.probe_error = err; 1086 rd.device_raw = null; 1087 } 1088 if (rd.device_raw) if (auto err = detect_valid_formats(&rd, valid_wave_format, dev_raw, 1089 AUDCLNT_SHAREMODE_EXCLUSIVE)) 1090 { 1091 rd.device_raw.probe_error = err; 1092 rd.device_raw = null; 1093 } 1094 if (rd.device_raw) if (auto err = detect_valid_layouts(&rd, valid_wave_format, dev_raw, 1095 AUDCLNT_SHAREMODE_EXCLUSIVE)) 1096 { 1097 rd.device_raw.probe_error = err; 1098 rd.device_raw = null; 1099 } 1100 1101 if (rd.wave_format) { 1102 CoTaskMemFree(rd.wave_format); 1103 rd.wave_format = null; 1104 } 1105 if (FAILED(hr = IAudioClient_GetMixFormat(rd.audio_client, cast(WAVEFORMATEX**)&rd.wave_format))) { 1106 // According to MSDN GetMixFormat only applies to shared-mode devices. 1107 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 1108 rd.device_shared = null; 1109 } 1110 else if(rd.wave_format && (rd.wave_format.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)) { 1111 rd.device_shared.probe_error = SoundIoError.OpeningDevice; 1112 rd.device_shared = null; 1113 } 1114 1115 if(rd.device_shared) { 1116 rd.device_shared.sample_rate_current = rd.wave_format.Format.nSamplesPerSec; 1117 rd.device_shared.current_format = from_wave_format_format(rd.wave_format); 1118 1119 if (rd.device_shared.aim == SoundIoDeviceAim.Output) { 1120 // For output streams in shared mode, 1121 // WASAPI performs resampling, so any value is valid. 1122 // Let's pick some reasonable min and max values. 1123 rd.device_shared.sample_rate_count = 1; 1124 rd.device_shared.sample_rates = &dev_shared.prealloc_sample_rate_range; 1125 rd.device_shared.sample_rates[0].min = soundio_int_min(SOUNDIO_MIN_SAMPLE_RATE, 1126 rd.device_shared.sample_rate_current); 1127 rd.device_shared.sample_rates[0].max = soundio_int_max(SOUNDIO_MAX_SAMPLE_RATE, 1128 rd.device_shared.sample_rate_current); 1129 } 1130 else { 1131 // Shared mode input stream: mix format is all we can do. 1132 rd.device_shared.sample_rate_count = 1; 1133 rd.device_shared.sample_rates = &dev_shared.prealloc_sample_rate_range; 1134 rd.device_shared.sample_rates[0].min = rd.device_shared.sample_rate_current; 1135 rd.device_shared.sample_rates[0].max = rd.device_shared.sample_rate_current; 1136 } 1137 1138 if (auto err = detect_valid_formats(&rd, rd.wave_format, dev_shared, 1139 AUDCLNT_SHAREMODE_SHARED)) 1140 { 1141 rd.device_shared.probe_error = err; 1142 rd.device_shared = null; 1143 } 1144 else { 1145 from_wave_format_layout(rd.wave_format, &rd.device_shared.current_layout); 1146 rd.device_shared.layout_count = 1; 1147 rd.device_shared.layouts = &rd.device_shared.current_layout; 1148 } 1149 } 1150 1151 IMMDevice_AddRef(rd.mm_device); 1152 dev_w_shared.mm_device = rd.mm_device; 1153 dev_w_raw.mm_device = rd.mm_device; 1154 rd.mm_device = null; 1155 1156 rd.device_shared = null; 1157 rd.device_raw = null; 1158 } 1159 1160 soundio_os_mutex_lock(siw.mutex); 1161 soundio_destroy_devices_info(siw.ready_devices_info); 1162 siw.ready_devices_info = rd.devices_info; 1163 siw.have_devices_flag = true; 1164 soundio_os_cond_signal(siw.cond, siw.mutex); 1165 soundio.on_events_signal(soundio); 1166 soundio_os_mutex_unlock(siw.mutex); 1167 1168 rd.devices_info = null; 1169 deinit_refresh_devices(&rd); 1170 1171 return 0; 1172 } 1173 1174 1175 static void shutdown_backend(SoundIoPrivate* si, int err) { 1176 SoundIo* soundio = &si.pub; 1177 SoundIoWasapi* siw = &si.backend_data.wasapi; 1178 soundio_os_mutex_lock(siw.mutex); 1179 siw.shutdown_err = err; 1180 soundio_os_cond_signal(siw.cond, siw.mutex); 1181 soundio.on_events_signal(soundio); 1182 soundio_os_mutex_unlock(siw.mutex); 1183 } 1184 1185 static void device_thread_run(void* arg) { 1186 SoundIoPrivate* si = cast(SoundIoPrivate*)arg; 1187 SoundIoWasapi* siw = &si.backend_data.wasapi; 1188 int err; 1189 1190 HRESULT hr = CoCreateInstance(CLSID_MMDEVICEENUMERATOR, null, 1191 CLSCTX_ALL, IID_IMMDEVICEENUMERATOR, cast(void**)&siw.device_enumerator); 1192 if (FAILED(hr)) { 1193 shutdown_backend(si, SoundIoError.SystemResources); 1194 return; 1195 } 1196 1197 if (FAILED(hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback( 1198 siw.device_enumerator, &siw.device_events))) 1199 { 1200 shutdown_backend(si, SoundIoError.SystemResources); 1201 return; 1202 } 1203 1204 soundio_os_mutex_lock(siw.scan_devices_mutex); 1205 for (;;) { 1206 if (siw.abort_flag) 1207 break; 1208 if (siw.device_scan_queued) { 1209 siw.device_scan_queued = false; 1210 soundio_os_mutex_unlock(siw.scan_devices_mutex); 1211 err = refresh_devices(si); 1212 if (err) { 1213 shutdown_backend(si, err); 1214 return; 1215 } 1216 soundio_os_mutex_lock(siw.scan_devices_mutex); 1217 continue; 1218 } 1219 soundio_os_cond_wait(siw.scan_devices_cond, siw.scan_devices_mutex); 1220 } 1221 soundio_os_mutex_unlock(siw.scan_devices_mutex); 1222 1223 IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(siw.device_enumerator, &siw.device_events); 1224 IMMDeviceEnumerator_Release(siw.device_enumerator); 1225 siw.device_enumerator = null; 1226 } 1227 1228 private extern(D) void my_flush_events(SoundIoPrivate* si, bool wait) { 1229 SoundIo* soundio = &si.pub; 1230 SoundIoWasapi* siw = &si.backend_data.wasapi; 1231 1232 bool change = false; 1233 bool cb_shutdown = false; 1234 SoundIoDevicesInfo* old_devices_info = null; 1235 1236 soundio_os_mutex_lock(siw.mutex); 1237 1238 // block until have devices 1239 while (wait || (!siw.have_devices_flag && !siw.shutdown_err)) { 1240 soundio_os_cond_wait(siw.cond, siw.mutex); 1241 wait = false; 1242 } 1243 1244 if (siw.shutdown_err && !siw.emitted_shutdown_cb) { 1245 siw.emitted_shutdown_cb = true; 1246 cb_shutdown = true; 1247 } else if (siw.ready_devices_info) { 1248 old_devices_info = si.safe_devices_info; 1249 si.safe_devices_info = siw.ready_devices_info; 1250 siw.ready_devices_info = null; 1251 change = true; 1252 } 1253 1254 soundio_os_mutex_unlock(siw.mutex); 1255 1256 if (cb_shutdown) 1257 soundio.on_backend_disconnect(soundio, siw.shutdown_err); 1258 else if (change) 1259 soundio.on_devices_change(soundio); 1260 1261 soundio_destroy_devices_info(old_devices_info); 1262 } 1263 1264 static void flush_events_wasapi(SoundIoPrivate* si) { 1265 my_flush_events(si, false); 1266 } 1267 1268 static void wait_events_wasapi(SoundIoPrivate* si) { 1269 my_flush_events(si, false); 1270 my_flush_events(si, true); 1271 } 1272 1273 static void wakeup_wasapi(SoundIoPrivate* si) { 1274 SoundIoWasapi* siw = &si.backend_data.wasapi; 1275 soundio_os_cond_signal(siw.cond, siw.mutex); 1276 } 1277 1278 static void force_device_scan_wasapi(SoundIoPrivate* si) { 1279 SoundIoWasapi* siw = &si.backend_data.wasapi; 1280 soundio_os_mutex_lock(siw.scan_devices_mutex); 1281 siw.device_scan_queued = true; 1282 soundio_os_cond_signal(siw.scan_devices_cond, siw.scan_devices_mutex); 1283 soundio_os_mutex_unlock(siw.scan_devices_mutex); 1284 } 1285 1286 static void outstream_thread_deinit(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 1287 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1288 1289 if (osw.audio_volume_control) 1290 IUnknown_Release(osw.audio_volume_control); 1291 if (osw.audio_render_client) 1292 IUnknown_Release(osw.audio_render_client); 1293 if (osw.audio_session_control) 1294 IUnknown_Release(osw.audio_session_control); 1295 if (osw.audio_clock_adjustment) 1296 IUnknown_Release(osw.audio_clock_adjustment); 1297 if (osw.audio_client) 1298 IUnknown_Release(osw.audio_client); 1299 } 1300 1301 static void outstream_destroy_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 1302 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1303 1304 if (osw.thread) { 1305 SOUNDIO_ATOMIC_FLAG_CLEAR(osw.thread_exit_flag); 1306 if (osw.h_event) 1307 SetEvent(osw.h_event); 1308 1309 soundio_os_mutex_lock(osw.mutex); 1310 soundio_os_cond_signal(osw.cond, osw.mutex); 1311 soundio_os_cond_signal(osw.start_cond, osw.mutex); 1312 soundio_os_mutex_unlock(osw.mutex); 1313 1314 soundio_os_thread_destroy(osw.thread); 1315 1316 osw.thread = null; 1317 } 1318 1319 if (osw.h_event) { 1320 CloseHandle(osw.h_event); 1321 osw.h_event = null; 1322 } 1323 1324 free(osw.stream_name); 1325 osw.stream_name = null; 1326 1327 soundio_os_cond_destroy(osw.cond); 1328 osw.cond = null; 1329 1330 soundio_os_cond_destroy(osw.start_cond); 1331 osw.start_cond = null; 1332 1333 soundio_os_mutex_destroy(osw.mutex); 1334 osw.mutex = null; 1335 } 1336 1337 static int outstream_do_open(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 1338 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1339 SoundIoOutStream* outstream = &os.pub; 1340 SoundIoDevice* device = outstream.device; 1341 SoundIoDevicePrivate* dev = cast(SoundIoDevicePrivate*)device; 1342 SoundIoDeviceWasapi* dw = &dev.backend_data.wasapi; 1343 HRESULT hr; 1344 1345 if (FAILED(hr = IMMDevice_Activate(dw.mm_device, IID_IAUDIOCLIENT, 1346 CLSCTX_ALL, null, cast(void**)&osw.audio_client))) 1347 { 1348 return SoundIoError.OpeningDevice; 1349 } 1350 1351 1352 AUDCLNT_SHAREMODE share_mode; 1353 DWORD flags; 1354 REFERENCE_TIME buffer_duration; 1355 REFERENCE_TIME periodicity; 1356 WAVEFORMATEXTENSIBLE wave_format = WAVEFORMATEXTENSIBLE.init; // TODO: equal to 0? 1357 wave_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; 1358 wave_format.Format.cbSize = WAVEFORMATEXTENSIBLE.sizeof - WAVEFORMATEX.sizeof; 1359 if (osw.is_raw) { 1360 wave_format.Format.nSamplesPerSec = outstream.sample_rate; 1361 flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; 1362 share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE; 1363 periodicity = to_reference_time(dw.period_duration); 1364 buffer_duration = periodicity; 1365 } else { 1366 WAVEFORMATEXTENSIBLE* mix_format; 1367 if (FAILED(hr = IAudioClient_GetMixFormat(osw.audio_client, cast(WAVEFORMATEX**)&mix_format))) { 1368 return SoundIoError.OpeningDevice; 1369 } 1370 wave_format.Format.nSamplesPerSec = cast(DWORD)outstream.sample_rate; 1371 osw.need_resample = (mix_format.Format.nSamplesPerSec != wave_format.Format.nSamplesPerSec); 1372 CoTaskMemFree(mix_format); 1373 mix_format = null; 1374 flags = osw.need_resample ? AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY : 0; 1375 share_mode = AUDCLNT_SHAREMODE_SHARED; 1376 periodicity = 0; 1377 buffer_duration = to_reference_time(4.0); 1378 } 1379 to_wave_format_layout(&outstream.layout, &wave_format); 1380 to_wave_format_format(outstream.format, &wave_format); 1381 complete_wave_format_data(&wave_format); 1382 1383 if (FAILED(hr = IAudioClient_Initialize(osw.audio_client, share_mode, flags, 1384 buffer_duration, periodicity, cast(WAVEFORMATEX*)&wave_format, null))) 1385 { 1386 if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { 1387 if (FAILED(hr = IAudioClient_GetBufferSize(osw.audio_client, &osw.buffer_frame_count))) { 1388 return SoundIoError.OpeningDevice; 1389 } 1390 IUnknown_Release(osw.audio_client); 1391 osw.audio_client = null; 1392 if (FAILED(hr = IMMDevice_Activate(dw.mm_device, IID_IAUDIOCLIENT, 1393 CLSCTX_ALL, null, cast(void**)&osw.audio_client))) 1394 { 1395 return SoundIoError.OpeningDevice; 1396 } 1397 if (!osw.is_raw) { 1398 WAVEFORMATEXTENSIBLE* mix_format; 1399 if (FAILED(hr = IAudioClient_GetMixFormat(osw.audio_client, cast(WAVEFORMATEX**)&mix_format))) { 1400 return SoundIoError.OpeningDevice; 1401 } 1402 wave_format.Format.nSamplesPerSec = cast(DWORD)outstream.sample_rate; 1403 osw.need_resample = (mix_format.Format.nSamplesPerSec != wave_format.Format.nSamplesPerSec); 1404 CoTaskMemFree(mix_format); 1405 mix_format = null; 1406 flags = osw.need_resample ? AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY : 0; 1407 to_wave_format_layout(&outstream.layout, &wave_format); 1408 to_wave_format_format(outstream.format, &wave_format); 1409 complete_wave_format_data(&wave_format); 1410 } 1411 1412 buffer_duration = to_reference_time(osw.buffer_frame_count / cast(double)outstream.sample_rate); 1413 if (osw.is_raw) 1414 periodicity = buffer_duration; 1415 if (FAILED(hr = IAudioClient_Initialize(osw.audio_client, share_mode, flags, 1416 buffer_duration, periodicity, cast(WAVEFORMATEX*)&wave_format, null))) 1417 { 1418 if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { 1419 return SoundIoError.IncompatibleDevice; 1420 } else if (hr == E_OUTOFMEMORY) { 1421 return SoundIoError.NoMem; 1422 } else { 1423 return SoundIoError.OpeningDevice; 1424 } 1425 } 1426 } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { 1427 return SoundIoError.IncompatibleDevice; 1428 } else if (hr == E_OUTOFMEMORY) { 1429 return SoundIoError.NoMem; 1430 } else { 1431 return SoundIoError.OpeningDevice; 1432 } 1433 } 1434 REFERENCE_TIME max_latency_ref_time; 1435 if (FAILED(hr = IAudioClient_GetStreamLatency(osw.audio_client, &max_latency_ref_time))) { 1436 return SoundIoError.OpeningDevice; 1437 } 1438 double max_latency_sec = from_reference_time(max_latency_ref_time); 1439 osw.min_padding_frames = cast(int) ((max_latency_sec * outstream.sample_rate) + 0.5); 1440 1441 1442 if (FAILED(hr = IAudioClient_GetBufferSize(osw.audio_client, &osw.buffer_frame_count))) { 1443 return SoundIoError.OpeningDevice; 1444 } 1445 outstream.software_latency = osw.buffer_frame_count / cast(double)outstream.sample_rate; 1446 1447 if (osw.is_raw) { 1448 if (FAILED(hr = IAudioClient_SetEventHandle(osw.audio_client, osw.h_event))) { 1449 return SoundIoError.OpeningDevice; 1450 } 1451 } 1452 1453 if (outstream.name) { 1454 if (FAILED(hr = IAudioClient_GetService(osw.audio_client, IID_IAUDIOSESSIONCONTROL, 1455 cast(void**)&osw.audio_session_control))) 1456 { 1457 return SoundIoError.OpeningDevice; 1458 } 1459 1460 if (auto err = to_lpwstr(outstream.name, cast(int) strlen(outstream.name), &osw.stream_name)) { 1461 return err; 1462 } 1463 if (FAILED(hr = IAudioSessionControl_SetDisplayName(osw.audio_session_control, 1464 osw.stream_name, null))) 1465 { 1466 return SoundIoError.OpeningDevice; 1467 } 1468 } 1469 1470 if (FAILED(hr = IAudioClient_GetService(osw.audio_client, IID_IAUDIORENDERCLIENT, 1471 cast(void**)&osw.audio_render_client))) 1472 { 1473 return SoundIoError.OpeningDevice; 1474 } 1475 1476 if (FAILED(hr = IAudioClient_GetService(osw.audio_client, IID_ISIMPLEAUDIOVOLUME, 1477 cast(void**)&osw.audio_volume_control))) 1478 { 1479 return SoundIoError.OpeningDevice; 1480 } 1481 1482 if (FAILED(hr = osw.audio_volume_control.lpVtbl.GetMasterVolume(osw.audio_volume_control, &outstream.volume))) 1483 { 1484 return SoundIoError.OpeningDevice; 1485 } 1486 1487 return 0; 1488 } 1489 1490 static void outstream_shared_run(SoundIoOutStreamPrivate* os) { 1491 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1492 SoundIoOutStream* outstream = &os.pub; 1493 1494 HRESULT hr; 1495 1496 UINT32 frames_used; 1497 if (FAILED(hr = IAudioClient_GetCurrentPadding(osw.audio_client, &frames_used))) { 1498 outstream.error_callback(outstream, SoundIoError.Streaming); 1499 return; 1500 } 1501 osw.writable_frame_count = osw.buffer_frame_count - frames_used; 1502 if (osw.writable_frame_count <= 0) { 1503 outstream.error_callback(outstream, SoundIoError.Streaming); 1504 return; 1505 } 1506 int frame_count_min = soundio_int_max(0, cast(int)osw.min_padding_frames - cast(int)frames_used); 1507 outstream.write_callback(outstream, frame_count_min, osw.writable_frame_count); 1508 1509 if (FAILED(hr = IAudioClient_Start(osw.audio_client))) { 1510 outstream.error_callback(outstream, SoundIoError.Streaming); 1511 return; 1512 } 1513 1514 for (;;) { 1515 if (FAILED(hr = IAudioClient_GetCurrentPadding(osw.audio_client, &frames_used))) { 1516 outstream.error_callback(outstream, SoundIoError.Streaming); 1517 return; 1518 } 1519 osw.writable_frame_count = osw.buffer_frame_count - frames_used; 1520 double time_until_underrun = frames_used / cast(double)outstream.sample_rate; 1521 double wait_time = time_until_underrun / 2.0; 1522 soundio_os_mutex_lock(osw.mutex); 1523 soundio_os_cond_timed_wait(osw.cond, osw.mutex, wait_time); 1524 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.thread_exit_flag)) { 1525 soundio_os_mutex_unlock(osw.mutex); 1526 return; 1527 } 1528 soundio_os_mutex_unlock(osw.mutex); 1529 bool reset_buffer = false; 1530 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.clear_buffer_flag)) { 1531 if (!osw.is_paused) { 1532 if (FAILED(hr = IAudioClient_Stop(osw.audio_client))) { 1533 outstream.error_callback(outstream, SoundIoError.Streaming); 1534 return; 1535 } 1536 osw.is_paused = true; 1537 } 1538 if (FAILED(hr = IAudioClient_Reset(osw.audio_client))) { 1539 outstream.error_callback(outstream, SoundIoError.Streaming); 1540 return; 1541 } 1542 SOUNDIO_ATOMIC_FLAG_CLEAR(osw.pause_resume_flag); 1543 reset_buffer = true; 1544 } 1545 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.pause_resume_flag)) { 1546 bool pause = SOUNDIO_ATOMIC_LOAD(osw.desired_pause_state); 1547 if (pause && !osw.is_paused) { 1548 if (FAILED(hr = IAudioClient_Stop(osw.audio_client))) { 1549 outstream.error_callback(outstream, SoundIoError.Streaming); 1550 return; 1551 } 1552 osw.is_paused = true; 1553 } else if (!pause && osw.is_paused) { 1554 if (FAILED(hr = IAudioClient_Start(osw.audio_client))) { 1555 outstream.error_callback(outstream, SoundIoError.Streaming); 1556 return; 1557 } 1558 osw.is_paused = false; 1559 } 1560 } 1561 1562 if (FAILED(hr = IAudioClient_GetCurrentPadding(osw.audio_client, &frames_used))) { 1563 outstream.error_callback(outstream, SoundIoError.Streaming); 1564 return; 1565 } 1566 osw.writable_frame_count = osw.buffer_frame_count - frames_used; 1567 if (osw.writable_frame_count > 0) { 1568 if (frames_used == 0 && !reset_buffer) 1569 outstream.underflow_callback(outstream); 1570 int frame_count_min1 = soundio_int_max(0, cast(int)osw.min_padding_frames - cast(int)frames_used); 1571 outstream.write_callback(outstream, frame_count_min1, osw.writable_frame_count); 1572 } 1573 } 1574 } 1575 1576 static void outstream_raw_run(SoundIoOutStreamPrivate* os) { 1577 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1578 SoundIoOutStream* outstream = &os.pub; 1579 1580 HRESULT hr; 1581 1582 outstream.write_callback(outstream, osw.buffer_frame_count, osw.buffer_frame_count); 1583 1584 if (FAILED(hr = IAudioClient_Start(osw.audio_client))) { 1585 outstream.error_callback(outstream, SoundIoError.Streaming); 1586 return; 1587 } 1588 1589 for (;;) { 1590 WaitForSingleObject(osw.h_event, INFINITE); 1591 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.thread_exit_flag)) 1592 return; 1593 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.pause_resume_flag)) { 1594 bool pause = SOUNDIO_ATOMIC_LOAD(osw.desired_pause_state); 1595 if (pause && !osw.is_paused) { 1596 if (FAILED(hr = IAudioClient_Stop(osw.audio_client))) { 1597 outstream.error_callback(outstream, SoundIoError.Streaming); 1598 return; 1599 } 1600 osw.is_paused = true; 1601 } else if (!pause && osw.is_paused) { 1602 if (FAILED(hr = IAudioClient_Start(osw.audio_client))) { 1603 outstream.error_callback(outstream, SoundIoError.Streaming); 1604 return; 1605 } 1606 osw.is_paused = false; 1607 } 1608 } 1609 1610 outstream.write_callback(outstream, osw.buffer_frame_count, osw.buffer_frame_count); 1611 } 1612 } 1613 1614 static void outstream_thread_run(void* arg) { 1615 SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)arg; 1616 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1617 SoundIoOutStream* outstream = &os.pub; 1618 SoundIoDevice* device = outstream.device; 1619 SoundIo* soundio = device.soundio; 1620 SoundIoPrivate* si = cast(SoundIoPrivate*)soundio; 1621 1622 if (auto err = outstream_do_open(si, os)) { 1623 outstream_thread_deinit(si, os); 1624 1625 soundio_os_mutex_lock(osw.mutex); 1626 osw.open_err = err; 1627 osw.open_complete = true; 1628 soundio_os_cond_signal(osw.cond, osw.mutex); 1629 soundio_os_mutex_unlock(osw.mutex); 1630 return; 1631 } 1632 1633 soundio_os_mutex_lock(osw.mutex); 1634 osw.open_complete = true; 1635 soundio_os_cond_signal(osw.cond, osw.mutex); 1636 for (;;) { 1637 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.thread_exit_flag)) { 1638 soundio_os_mutex_unlock(osw.mutex); 1639 return; 1640 } 1641 if (osw.started) { 1642 soundio_os_mutex_unlock(osw.mutex); 1643 break; 1644 } 1645 soundio_os_cond_wait(osw.start_cond, osw.mutex); 1646 } 1647 1648 if (osw.is_raw) 1649 outstream_raw_run(os); 1650 else 1651 outstream_shared_run(os); 1652 1653 outstream_thread_deinit(si, os); 1654 } 1655 1656 static int outstream_open_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 1657 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1658 SoundIoOutStream* outstream = &os.pub; 1659 SoundIoDevice* device = outstream.device; 1660 SoundIo* soundio = &si.pub; 1661 1662 SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.pause_resume_flag); 1663 SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.clear_buffer_flag); 1664 SOUNDIO_ATOMIC_STORE(osw.desired_pause_state, false); 1665 1666 // All the COM functions are supposed to be called from the same thread. libsoundio API does not 1667 // restrict the calling thread context in this way. Furthermore, the user might have called 1668 // CoInitializeEx with a different threading model than Single Threaded Apartment. 1669 // So we create a thread to do all the initialization and teardown, and communicate state 1670 // via conditions and signals. The thread for initialization and teardown is also used 1671 // for the realtime code calls the user write_callback. 1672 1673 osw.is_raw = device.is_raw; 1674 1675 if (!cast(bool)(osw.cond = soundio_os_cond_create())) { 1676 outstream_destroy_wasapi(si, os); 1677 return SoundIoError.NoMem; 1678 } 1679 1680 if (!cast(bool)(osw.start_cond = soundio_os_cond_create())) { 1681 outstream_destroy_wasapi(si, os); 1682 return SoundIoError.NoMem; 1683 } 1684 1685 if (!cast(bool)(osw.mutex = soundio_os_mutex_create())) { 1686 outstream_destroy_wasapi(si, os); 1687 return SoundIoError.NoMem; 1688 } 1689 1690 if (osw.is_raw) { 1691 osw.h_event = CreateEvent(null, FALSE, FALSE, null); 1692 if (!osw.h_event) { 1693 outstream_destroy_wasapi(si, os); 1694 return SoundIoError.OpeningDevice; 1695 } 1696 } 1697 1698 SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osw.thread_exit_flag); 1699 if (auto err = soundio_os_thread_create(&outstream_thread_run, os, 1700 soundio.emit_rtprio_warning, &osw.thread)) 1701 { 1702 outstream_destroy_wasapi(si, os); 1703 return err; 1704 } 1705 1706 soundio_os_mutex_lock(osw.mutex); 1707 while (!osw.open_complete) 1708 soundio_os_cond_wait(osw.cond, osw.mutex); 1709 soundio_os_mutex_unlock(osw.mutex); 1710 1711 if (osw.open_err) { 1712 outstream_destroy_wasapi(si, os); 1713 return osw.open_err; 1714 } 1715 1716 return 0; 1717 } 1718 1719 static int outstream_pause_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, bool pause) { 1720 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1721 1722 SOUNDIO_ATOMIC_STORE(osw.desired_pause_state, pause); 1723 SOUNDIO_ATOMIC_FLAG_CLEAR(osw.pause_resume_flag); 1724 if (osw.h_event) { 1725 SetEvent(osw.h_event); 1726 } else { 1727 soundio_os_mutex_lock(osw.mutex); 1728 soundio_os_cond_signal(osw.cond, osw.mutex); 1729 soundio_os_mutex_unlock(osw.mutex); 1730 } 1731 1732 return 0; 1733 } 1734 1735 static int outstream_start_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 1736 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1737 1738 soundio_os_mutex_lock(osw.mutex); 1739 osw.started = true; 1740 soundio_os_cond_signal(osw.start_cond, osw.mutex); 1741 soundio_os_mutex_unlock(osw.mutex); 1742 1743 return 0; 1744 } 1745 1746 static int outstream_begin_write_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, SoundIoChannelArea** out_areas, int* frame_count) { 1747 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1748 SoundIoOutStream* outstream = &os.pub; 1749 HRESULT hr; 1750 1751 osw.write_frame_count = *frame_count; 1752 1753 1754 char* data; 1755 if (FAILED(hr = IAudioRenderClient_GetBuffer(osw.audio_render_client, 1756 osw.write_frame_count, cast(BYTE**)&data))) 1757 { 1758 return SoundIoError.Streaming; 1759 } 1760 1761 for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) { 1762 osw.areas[ch].ptr = data + ch * outstream.bytes_per_sample; 1763 osw.areas[ch].step = outstream.bytes_per_frame; 1764 } 1765 1766 *out_areas = osw.areas.ptr; 1767 1768 return 0; 1769 } 1770 1771 static int outstream_end_write_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 1772 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1773 HRESULT hr; 1774 if (FAILED(hr = IAudioRenderClient_ReleaseBuffer(osw.audio_render_client, osw.write_frame_count, 0))) { 1775 return SoundIoError.Streaming; 1776 } 1777 return 0; 1778 } 1779 1780 static int outstream_clear_buffer_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) { 1781 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1782 1783 if (osw.h_event) { 1784 return SoundIoError.IncompatibleDevice; 1785 } else { 1786 SOUNDIO_ATOMIC_FLAG_CLEAR(osw.clear_buffer_flag); 1787 soundio_os_mutex_lock(osw.mutex); 1788 soundio_os_cond_signal(osw.cond, osw.mutex); 1789 soundio_os_mutex_unlock(osw.mutex); 1790 } 1791 1792 return 0; 1793 } 1794 1795 static int outstream_get_latency_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, double* out_latency) { 1796 SoundIoOutStream* outstream = &os.pub; 1797 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1798 1799 HRESULT hr; 1800 UINT32 frames_used; 1801 if (FAILED(hr = IAudioClient_GetCurrentPadding(osw.audio_client, &frames_used))) { 1802 return SoundIoError.Streaming; 1803 } 1804 1805 *out_latency = frames_used / cast(double)outstream.sample_rate; 1806 return 0; 1807 } 1808 1809 static int outstream_set_volume_wasapi(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, float volume) { 1810 SoundIoOutStream* outstream = &os.pub; 1811 SoundIoOutStreamWasapi* osw = &os.backend_data.wasapi; 1812 1813 HRESULT hr; 1814 if (FAILED(hr = osw.audio_volume_control.lpVtbl.SetMasterVolume(osw.audio_volume_control, volume, null))) 1815 { 1816 return SoundIoError.IncompatibleDevice; 1817 } 1818 1819 outstream.volume = volume; 1820 return 0; 1821 } 1822 1823 static void instream_thread_deinit(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) { 1824 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 1825 1826 if (isw.audio_capture_client) 1827 IUnknown_Release(isw.audio_capture_client); 1828 if (isw.audio_client) 1829 IUnknown_Release(isw.audio_client); 1830 } 1831 1832 1833 static void instream_destroy_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) { 1834 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 1835 1836 if (isw.thread) { 1837 SOUNDIO_ATOMIC_FLAG_CLEAR(isw.thread_exit_flag); 1838 if (isw.h_event) 1839 SetEvent(isw.h_event); 1840 1841 soundio_os_mutex_lock(isw.mutex); 1842 soundio_os_cond_signal(isw.cond, isw.mutex); 1843 soundio_os_cond_signal(isw.start_cond, isw.mutex); 1844 soundio_os_mutex_unlock(isw.mutex); 1845 soundio_os_thread_destroy(isw.thread); 1846 1847 isw.thread = null; 1848 } 1849 1850 if (isw.h_event) { 1851 CloseHandle(isw.h_event); 1852 isw.h_event = null; 1853 } 1854 1855 soundio_os_cond_destroy(isw.cond); 1856 isw.cond = null; 1857 1858 soundio_os_cond_destroy(isw.start_cond); 1859 isw.start_cond = null; 1860 1861 soundio_os_mutex_destroy(isw.mutex); 1862 isw.mutex = null; 1863 } 1864 1865 static int instream_do_open(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) { 1866 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 1867 SoundIoInStream* instream = &is_.pub; 1868 SoundIoDevice* device = instream.device; 1869 SoundIoDevicePrivate* dev = cast(SoundIoDevicePrivate*)device; 1870 SoundIoDeviceWasapi* dw = &dev.backend_data.wasapi; 1871 HRESULT hr; 1872 1873 if (FAILED(hr = IMMDevice_Activate(dw.mm_device, IID_IAUDIOCLIENT, 1874 CLSCTX_ALL, null, cast(void**)&isw.audio_client))) 1875 { 1876 return SoundIoError.OpeningDevice; 1877 } 1878 1879 AUDCLNT_SHAREMODE share_mode; 1880 DWORD flags; 1881 REFERENCE_TIME buffer_duration; 1882 REFERENCE_TIME periodicity; 1883 WAVEFORMATEXTENSIBLE wave_format = WAVEFORMATEXTENSIBLE.init; // todo: zero? 1884 wave_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; 1885 wave_format.Format.cbSize = WAVEFORMATEXTENSIBLE.sizeof - WAVEFORMATEX.sizeof; 1886 if (isw.is_raw) { 1887 wave_format.Format.nSamplesPerSec = instream.sample_rate; 1888 flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; 1889 share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE; 1890 periodicity = to_reference_time(dw.period_duration); 1891 buffer_duration = periodicity; 1892 } else { 1893 WAVEFORMATEXTENSIBLE* mix_format; 1894 if (FAILED(hr = IAudioClient_GetMixFormat(isw.audio_client, cast(WAVEFORMATEX**)&mix_format))) { 1895 return SoundIoError.OpeningDevice; 1896 } 1897 wave_format.Format.nSamplesPerSec = mix_format.Format.nSamplesPerSec; 1898 CoTaskMemFree(mix_format); 1899 mix_format = null; 1900 if (wave_format.Format.nSamplesPerSec != cast(DWORD)instream.sample_rate) { 1901 return SoundIoError.IncompatibleDevice; 1902 } 1903 flags = 0; 1904 share_mode = AUDCLNT_SHAREMODE_SHARED; 1905 periodicity = 0; 1906 buffer_duration = to_reference_time(4.0); 1907 } 1908 to_wave_format_layout(&instream.layout, &wave_format); 1909 to_wave_format_format(instream.format, &wave_format); 1910 complete_wave_format_data(&wave_format); 1911 1912 if (FAILED(hr = IAudioClient_Initialize(isw.audio_client, share_mode, flags, 1913 buffer_duration, periodicity, cast(WAVEFORMATEX*)&wave_format, null))) 1914 { 1915 if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { 1916 if (FAILED(hr = IAudioClient_GetBufferSize(isw.audio_client, &isw.buffer_frame_count))) { 1917 return SoundIoError.OpeningDevice; 1918 } 1919 IUnknown_Release(isw.audio_client); 1920 isw.audio_client = null; 1921 if (FAILED(hr = IMMDevice_Activate(dw.mm_device, IID_IAUDIOCLIENT, 1922 CLSCTX_ALL, null, cast(void**)&isw.audio_client))) 1923 { 1924 return SoundIoError.OpeningDevice; 1925 } 1926 if (!isw.is_raw) { 1927 WAVEFORMATEXTENSIBLE* mix_format; 1928 if (FAILED(hr = IAudioClient_GetMixFormat(isw.audio_client, cast(WAVEFORMATEX**)&mix_format))) { 1929 return SoundIoError.OpeningDevice; 1930 } 1931 wave_format.Format.nSamplesPerSec = mix_format.Format.nSamplesPerSec; 1932 CoTaskMemFree(mix_format); 1933 mix_format = null; 1934 flags = 0; 1935 to_wave_format_layout(&instream.layout, &wave_format); 1936 to_wave_format_format(instream.format, &wave_format); 1937 complete_wave_format_data(&wave_format); 1938 } 1939 1940 buffer_duration = to_reference_time(isw.buffer_frame_count / cast(double)instream.sample_rate); 1941 if (isw.is_raw) 1942 periodicity = buffer_duration; 1943 if (FAILED(hr = IAudioClient_Initialize(isw.audio_client, share_mode, flags, 1944 buffer_duration, periodicity, cast(WAVEFORMATEX*)&wave_format, null))) 1945 { 1946 if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { 1947 return SoundIoError.IncompatibleDevice; 1948 } else if (hr == E_OUTOFMEMORY) { 1949 return SoundIoError.NoMem; 1950 } else { 1951 return SoundIoError.OpeningDevice; 1952 } 1953 } 1954 } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { 1955 return SoundIoError.IncompatibleDevice; 1956 } else if (hr == E_OUTOFMEMORY) { 1957 return SoundIoError.NoMem; 1958 } else { 1959 return SoundIoError.OpeningDevice; 1960 } 1961 } 1962 if (FAILED(hr = IAudioClient_GetBufferSize(isw.audio_client, &isw.buffer_frame_count))) { 1963 return SoundIoError.OpeningDevice; 1964 } 1965 if (instream.software_latency == 0.0) 1966 instream.software_latency = 1.0; 1967 instream.software_latency = soundio_double_clamp(device.software_latency_min, 1968 instream.software_latency, device.software_latency_max); 1969 if (isw.is_raw) 1970 instream.software_latency = isw.buffer_frame_count / cast(double)instream.sample_rate; 1971 1972 if (isw.is_raw) { 1973 if (FAILED(hr = IAudioClient_SetEventHandle(isw.audio_client, isw.h_event))) { 1974 return SoundIoError.OpeningDevice; 1975 } 1976 } 1977 1978 if (instream.name) { 1979 if (FAILED(hr = IAudioClient_GetService(isw.audio_client, IID_IAUDIOSESSIONCONTROL, 1980 cast(void**)&isw.audio_session_control))) 1981 { 1982 return SoundIoError.OpeningDevice; 1983 } 1984 1985 if (auto err = to_lpwstr(instream.name, cast(int) strlen(instream.name), &isw.stream_name)) { 1986 return err; 1987 } 1988 if (FAILED(hr = IAudioSessionControl_SetDisplayName(isw.audio_session_control, 1989 isw.stream_name, null))) 1990 { 1991 return SoundIoError.OpeningDevice; 1992 } 1993 } 1994 1995 if (FAILED(hr = IAudioClient_GetService(isw.audio_client, IID_IAUDIOCAPTURECLIENT, 1996 cast(void**)&isw.audio_capture_client))) 1997 { 1998 return SoundIoError.OpeningDevice; 1999 } 2000 2001 return 0; 2002 } 2003 2004 static void instream_raw_run(SoundIoInStreamPrivate* is_) { 2005 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 2006 SoundIoInStream* instream = &is_.pub; 2007 2008 HRESULT hr; 2009 2010 if (FAILED(hr = IAudioClient_Start(isw.audio_client))) { 2011 instream.error_callback(instream, SoundIoError.Streaming); 2012 return; 2013 } 2014 2015 for (;;) { 2016 WaitForSingleObject(isw.h_event, INFINITE); 2017 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isw.thread_exit_flag)) 2018 return; 2019 2020 instream.read_callback(instream, isw.buffer_frame_count, isw.buffer_frame_count); 2021 } 2022 } 2023 2024 static void instream_shared_run(SoundIoInStreamPrivate* is_) { 2025 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 2026 SoundIoInStream* instream = &is_.pub; 2027 2028 HRESULT hr; 2029 2030 if (FAILED(hr = IAudioClient_Start(isw.audio_client))) { 2031 instream.error_callback(instream, SoundIoError.Streaming); 2032 return; 2033 } 2034 2035 for (;;) { 2036 soundio_os_mutex_lock(isw.mutex); 2037 soundio_os_cond_timed_wait(isw.cond, isw.mutex, instream.software_latency / 2.0); 2038 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isw.thread_exit_flag)) { 2039 soundio_os_mutex_unlock(isw.mutex); 2040 return; 2041 } 2042 soundio_os_mutex_unlock(isw.mutex); 2043 2044 UINT32 frames_available; 2045 if (FAILED(hr = IAudioClient_GetCurrentPadding(isw.audio_client, &frames_available))) { 2046 instream.error_callback(instream, SoundIoError.Streaming); 2047 return; 2048 } 2049 2050 isw.readable_frame_count = frames_available; 2051 if (isw.readable_frame_count > 0) 2052 instream.read_callback(instream, 0, isw.readable_frame_count); 2053 } 2054 } 2055 2056 static void instream_thread_run(void* arg) { 2057 SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)arg; 2058 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 2059 SoundIoInStream* instream = &is_.pub; 2060 SoundIoDevice* device = instream.device; 2061 SoundIo* soundio = device.soundio; 2062 SoundIoPrivate* si = cast(SoundIoPrivate*)soundio; 2063 2064 if (auto err = instream_do_open(si, is_)) { 2065 instream_thread_deinit(si, is_); 2066 2067 soundio_os_mutex_lock(isw.mutex); 2068 isw.open_err = err; 2069 isw.open_complete = true; 2070 soundio_os_cond_signal(isw.cond, isw.mutex); 2071 soundio_os_mutex_unlock(isw.mutex); 2072 return; 2073 } 2074 2075 soundio_os_mutex_lock(isw.mutex); 2076 isw.open_complete = true; 2077 soundio_os_cond_signal(isw.cond, isw.mutex); 2078 for (;;) { 2079 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isw.thread_exit_flag)) { 2080 soundio_os_mutex_unlock(isw.mutex); 2081 return; 2082 } 2083 if (isw.started) { 2084 soundio_os_mutex_unlock(isw.mutex); 2085 break; 2086 } 2087 soundio_os_cond_wait(isw.start_cond, isw.mutex); 2088 } 2089 2090 if (isw.is_raw) 2091 instream_raw_run(is_); 2092 else 2093 instream_shared_run(is_); 2094 2095 instream_thread_deinit(si, is_); 2096 } 2097 2098 static int instream_open_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) { 2099 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 2100 SoundIoInStream* instream = &is_.pub; 2101 SoundIoDevice* device = instream.device; 2102 SoundIo* soundio = &si.pub; 2103 2104 // All the COM functions are supposed to be called from the same thread. libsoundio API does not 2105 // restrict the calling thread context in this way. Furthermore, the user might have called 2106 // CoInitializeEx with a different threading model than Single Threaded Apartment. 2107 // So we create a thread to do all the initialization and teardown, and communicate state 2108 // via conditions and signals. The thread for initialization and teardown is also used 2109 // for the realtime code calls the user write_callback. 2110 2111 isw.is_raw = device.is_raw; 2112 2113 isw.cond = soundio_os_cond_create(); 2114 if (!isw.cond) { 2115 instream_destroy_wasapi(si, is_); 2116 return SoundIoError.NoMem; 2117 } 2118 2119 isw.start_cond = soundio_os_cond_create(); 2120 if (!isw.start_cond) { 2121 instream_destroy_wasapi(si, is_); 2122 return SoundIoError.NoMem; 2123 } 2124 2125 isw.mutex = soundio_os_mutex_create(); 2126 if (!isw.mutex) { 2127 instream_destroy_wasapi(si, is_); 2128 return SoundIoError.NoMem; 2129 } 2130 2131 if (isw.is_raw) { 2132 isw.h_event = CreateEvent(null, FALSE, FALSE, null); 2133 if (!isw.h_event) { 2134 instream_destroy_wasapi(si, is_); 2135 return SoundIoError.OpeningDevice; 2136 } 2137 } 2138 2139 SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isw.thread_exit_flag); 2140 if (auto err = soundio_os_thread_create(&instream_thread_run, is_, 2141 soundio.emit_rtprio_warning, &isw.thread)) 2142 { 2143 instream_destroy_wasapi(si, is_); 2144 return err; 2145 } 2146 2147 soundio_os_mutex_lock(isw.mutex); 2148 while (!isw.open_complete) 2149 soundio_os_cond_wait(isw.cond, isw.mutex); 2150 soundio_os_mutex_unlock(isw.mutex); 2151 2152 if (isw.open_err) { 2153 instream_destroy_wasapi(si, is_); 2154 return isw.open_err; 2155 } 2156 2157 return 0; 2158 } 2159 2160 static int instream_pause_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, bool pause) { 2161 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 2162 HRESULT hr; 2163 if (pause && !isw.is_paused) { 2164 if (FAILED(hr = IAudioClient_Stop(isw.audio_client))) 2165 return SoundIoError.Streaming; 2166 isw.is_paused = true; 2167 } else if (!pause && isw.is_paused) { 2168 if (FAILED(hr = IAudioClient_Start(isw.audio_client))) 2169 return SoundIoError.Streaming; 2170 isw.is_paused = false; 2171 } 2172 return 0; 2173 } 2174 2175 static int instream_start_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) { 2176 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 2177 2178 soundio_os_mutex_lock(isw.mutex); 2179 isw.started = true; 2180 soundio_os_cond_signal(isw.start_cond, isw.mutex); 2181 soundio_os_mutex_unlock(isw.mutex); 2182 2183 return 0; 2184 } 2185 2186 static int instream_begin_read_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, SoundIoChannelArea** out_areas, int* frame_count) { 2187 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 2188 SoundIoInStream* instream = &is_.pub; 2189 HRESULT hr; 2190 2191 if (isw.read_buf_frames_left <= 0) { 2192 UINT32 frames_to_read; 2193 DWORD flags; 2194 if (FAILED(hr = IAudioCaptureClient_GetBuffer(isw.audio_capture_client, 2195 cast(BYTE**)&isw.read_buf, &frames_to_read, &flags, null, null))) 2196 { 2197 return SoundIoError.Streaming; 2198 } 2199 isw.opened_buf_frames = frames_to_read; 2200 isw.read_buf_frames_left = frames_to_read; 2201 2202 if (flags & AUDCLNT_BUFFERFLAGS_SILENT) 2203 isw.read_buf = null; 2204 } 2205 2206 isw.read_frame_count = soundio_int_min(*frame_count, isw.read_buf_frames_left); 2207 *frame_count = isw.read_frame_count; 2208 2209 if (isw.read_buf) { 2210 for (int ch = 0; ch < instream.layout.channel_count; ch += 1) { 2211 isw.areas[ch].ptr = isw.read_buf + ch * instream.bytes_per_sample; 2212 isw.areas[ch].step = instream.bytes_per_frame; 2213 2214 isw.areas[ch].ptr += instream.bytes_per_frame * (isw.opened_buf_frames - isw.read_buf_frames_left); 2215 } 2216 2217 *out_areas = isw.areas.ptr; 2218 } else { 2219 *out_areas = null; 2220 } 2221 2222 return 0; 2223 } 2224 2225 static int instream_end_read_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) { 2226 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 2227 HRESULT hr; 2228 2229 isw.read_buf_frames_left -= isw.read_frame_count; 2230 2231 if (isw.read_buf_frames_left <= 0) { 2232 if (FAILED(hr = IAudioCaptureClient_ReleaseBuffer(isw.audio_capture_client, isw.opened_buf_frames))) { 2233 return SoundIoError.Streaming; 2234 } 2235 } 2236 2237 return 0; 2238 } 2239 2240 static int instream_get_latency_wasapi(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, double* out_latency) { 2241 SoundIoInStream* instream = &is_.pub; 2242 SoundIoInStreamWasapi* isw = &is_.backend_data.wasapi; 2243 2244 HRESULT hr; 2245 UINT32 frames_used; 2246 if (FAILED(hr = IAudioClient_GetCurrentPadding(isw.audio_client, &frames_used))) { 2247 return SoundIoError.Streaming; 2248 } 2249 2250 *out_latency = frames_used / cast(double)instream.sample_rate; 2251 return 0; 2252 } 2253 2254 2255 static void destroy_wasapi(SoundIoPrivate* si) { 2256 SoundIoWasapi* siw = &si.backend_data.wasapi; 2257 2258 if (siw.thread) { 2259 soundio_os_mutex_lock(siw.scan_devices_mutex); 2260 siw.abort_flag = true; 2261 soundio_os_cond_signal(siw.scan_devices_cond, siw.scan_devices_mutex); 2262 soundio_os_mutex_unlock(siw.scan_devices_mutex); 2263 soundio_os_thread_destroy(siw.thread); 2264 } 2265 2266 if (siw.cond) 2267 soundio_os_cond_destroy(siw.cond); 2268 2269 if (siw.scan_devices_cond) 2270 soundio_os_cond_destroy(siw.scan_devices_cond); 2271 2272 if (siw.scan_devices_mutex) 2273 soundio_os_mutex_destroy(siw.scan_devices_mutex); 2274 2275 if (siw.mutex) 2276 soundio_os_mutex_destroy(siw.mutex); 2277 2278 soundio_destroy_devices_info(siw.ready_devices_info); 2279 } 2280 2281 pragma(inline, true) static shared(SoundIoPrivate)* soundio_MMNotificationClient_si(IMMNotificationClient* client) { 2282 auto siw = cast(SoundIoWasapi*)((cast(ubyte*)client) - SoundIoWasapi.device_events.offsetof); 2283 auto si = cast(shared(SoundIoPrivate)*)((cast(ubyte*)siw) - SoundIoPrivate.backend_data.offsetof); 2284 return si; 2285 } 2286 2287 static extern(Windows) HRESULT soundio_MMNotificationClient_QueryInterface(IMMNotificationClient* client, REFIID riid, void** ppv) { 2288 if (IS_EQUAL_IID(riid, &IID_IUnknown) || IS_EQUAL_IID(riid, &IID_IMMNotificationClient)) { 2289 *ppv = client; 2290 IUnknown_AddRef(client); 2291 return S_OK; 2292 } else { 2293 *ppv = null; 2294 return E_NOINTERFACE; 2295 } 2296 } 2297 2298 static extern(Windows) ULONG soundio_MMNotificationClient_AddRef(IMMNotificationClient* client) { 2299 shared SoundIoPrivate* si = soundio_MMNotificationClient_si(client); 2300 shared SoundIoWasapi* siw = &si.backend_data.wasapi; 2301 // workaround because `InterlockedIncrement` is missing in D's Kernel32 import library 2302 return atomicOp!"+="(siw.device_events_refs, 1); 2303 //return InterlockedIncrement(&siw.device_events_refs); 2304 } 2305 2306 static extern(Windows) ULONG soundio_MMNotificationClient_Release(IMMNotificationClient* client) { 2307 shared SoundIoPrivate* si = soundio_MMNotificationClient_si(client); 2308 shared SoundIoWasapi* siw = &si.backend_data.wasapi; 2309 // workaround because `InterlockedDecrement` is missing in D's Kernel32 import library 2310 return atomicOp!"-="(siw.device_events_refs, 1); 2311 //return InterlockedDecrement(&siw.device_events_refs); 2312 } 2313 2314 static HRESULT queue_device_scan(IMMNotificationClient* client) { 2315 auto si = cast(SoundIoPrivate*) soundio_MMNotificationClient_si(client); // cast away shared 2316 force_device_scan_wasapi(si); 2317 return S_OK; 2318 } 2319 2320 static extern(Windows) HRESULT soundio_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient* client, LPCWSTR wid, DWORD state) { 2321 return queue_device_scan(client); 2322 } 2323 2324 static extern(Windows) HRESULT soundio_MMNotificationClient_OnDeviceAdded(IMMNotificationClient* client, LPCWSTR wid) { 2325 return queue_device_scan(client); 2326 } 2327 2328 static extern(Windows) HRESULT soundio_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient* client, LPCWSTR wid) { 2329 return queue_device_scan(client); 2330 } 2331 2332 static extern(Windows) HRESULT soundio_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient* client, EDataFlow flow, ERole role, LPCWSTR wid) { 2333 return queue_device_scan(client); 2334 } 2335 2336 static extern(Windows) HRESULT soundio_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient* client, LPCWSTR wid, const(PROPERTYKEY) key) { 2337 return queue_device_scan(client); 2338 } 2339 2340 2341 static IMMNotificationClientVtbl soundio_MMNotificationClient = IMMNotificationClientVtbl( 2342 &soundio_MMNotificationClient_QueryInterface, 2343 &soundio_MMNotificationClient_AddRef, 2344 &soundio_MMNotificationClient_Release, 2345 &soundio_MMNotificationClient_OnDeviceStateChanged, 2346 &soundio_MMNotificationClient_OnDeviceAdded, 2347 &soundio_MMNotificationClient_OnDeviceRemoved, 2348 &soundio_MMNotificationClient_OnDefaultDeviceChange, 2349 &soundio_MMNotificationClient_OnPropertyValueChanged, 2350 ); 2351 2352 package int soundio_wasapi_init(SoundIoPrivate* si) { 2353 SoundIoWasapi* siw = &si.backend_data.wasapi; 2354 2355 siw.device_scan_queued = true; 2356 2357 siw.mutex = soundio_os_mutex_create(); 2358 if (!siw.mutex) { 2359 destroy_wasapi(si); 2360 return SoundIoError.NoMem; 2361 } 2362 2363 siw.scan_devices_mutex = soundio_os_mutex_create(); 2364 if (!siw.scan_devices_mutex) { 2365 destroy_wasapi(si); 2366 return SoundIoError.NoMem; 2367 } 2368 2369 siw.cond = soundio_os_cond_create(); 2370 if (!siw.cond) { 2371 destroy_wasapi(si); 2372 return SoundIoError.NoMem; 2373 } 2374 2375 siw.scan_devices_cond = soundio_os_cond_create(); 2376 if (!siw.scan_devices_cond) { 2377 destroy_wasapi(si); 2378 return SoundIoError.NoMem; 2379 } 2380 2381 siw.device_events.lpVtbl = &soundio_MMNotificationClient; 2382 siw.device_events_refs = 1; 2383 2384 if (auto err = soundio_os_thread_create(&device_thread_run, si, null, &siw.thread)) { 2385 destroy_wasapi(si); 2386 return err; 2387 } 2388 2389 si.destroy = &destroy_wasapi; 2390 si.flush_events = &flush_events_wasapi; 2391 si.wait_events = &wait_events_wasapi; 2392 si.wakeup = &wakeup_wasapi; 2393 si.force_device_scan = &force_device_scan_wasapi; 2394 2395 si.outstream_open = &outstream_open_wasapi; 2396 si.outstream_destroy = &outstream_destroy_wasapi; 2397 si.outstream_start = &outstream_start_wasapi; 2398 si.outstream_begin_write = &outstream_begin_write_wasapi; 2399 si.outstream_end_write = &outstream_end_write_wasapi; 2400 si.outstream_clear_buffer = &outstream_clear_buffer_wasapi; 2401 si.outstream_pause = &outstream_pause_wasapi; 2402 si.outstream_get_latency = &outstream_get_latency_wasapi; 2403 si.outstream_set_volume = &outstream_set_volume_wasapi; 2404 2405 si.instream_open = &instream_open_wasapi; 2406 si.instream_destroy = &instream_destroy_wasapi; 2407 si.instream_start = &instream_start_wasapi; 2408 si.instream_begin_read = &instream_begin_read_wasapi; 2409 si.instream_end_read = &instream_end_read_wasapi; 2410 si.instream_pause = &instream_pause_wasapi; 2411 si.instream_get_latency = &instream_get_latency_wasapi; 2412 2413 return 0; 2414 }