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