1 /* RetroArch - A frontend for libretro.
2 * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
3 * Copyright (C) 2011-2017 - Daniel De Matteis
4 *
5 * RetroArch is free software: you can redistribute it and/or modify it under the terms
6 * of the GNU General Public License as published by the Free Software Found-
7 * ation, either version 3 of the License, or (at your option) any later version.
8 *
9 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with RetroArch.
14 * If not, see <http://www.gnu.org/licenses/>.
15 */
16
17 #include <stdint.h>
18 #include <stdlib.h>
19 #include <stddef.h>
20 #include <string.h>
21
22 #ifndef _XBOX
23 #include <windows.h>
24 #include <mmsystem.h>
25 #include <mmreg.h>
26 #endif
27
28 #include <dsound.h>
29
30 #include <boolean.h>
31
32 #include <retro_inline.h>
33 #include <retro_miscellaneous.h>
34 #include <retro_timers.h>
35 #ifdef HAVE_THREADS
36 #include <rthreads/rthreads.h>
37 #endif
38 #include <lists/string_list.h>
39 #include <queues/fifo_queue.h>
40 #include <string/stdstring.h>
41
42 #include "../../retroarch.h"
43 #include "../../verbosity.h"
44
45 #ifdef _XBOX
46 #define DSERR_BUFFERLOST MAKE_DSHRESULT(150)
47 #define DSERR_INVALIDPARAM E_INVALIDARG
48 #define DSERR_PRIOLEVELNEEDED MAKE_DSHRESULT(70)
49 #endif
50
51 #if defined(_MSC_VER) && !defined(_XBOX)
52 #pragma comment(lib, "dsound")
53 #pragma comment(lib, "dxguid")
54 #endif
55
56 typedef struct dsound
57 {
58 LPDIRECTSOUND ds;
59 LPDIRECTSOUNDBUFFER dsb;
60
61 fifo_buffer_t *buffer;
62 CRITICAL_SECTION crit;
63
64 HANDLE event;
65 #ifdef HAVE_THREADS
66 sthread_t *thread;
67 #else
68 HANDLE thread;
69 #endif
70
71 unsigned buffer_size;
72
73 bool nonblock;
74 bool is_paused;
75 volatile bool thread_alive;
76 } dsound_t;
77
78 /* Forward declarations */
79 static void *dsound_list_new(void *u);
80
write_avail(unsigned read_ptr,unsigned write_ptr,unsigned buffer_size)81 static INLINE unsigned write_avail(unsigned read_ptr,
82 unsigned write_ptr, unsigned buffer_size)
83 {
84 return (read_ptr + buffer_size - write_ptr) % buffer_size;
85 }
86
87 #define CHUNK_SIZE 256
88
89 struct audio_lock
90 {
91 void *chunk1;
92 void *chunk2;
93 DWORD size1;
94 DWORD size2;
95 };
96
grab_region(dsound_t * ds,uint32_t write_ptr,struct audio_lock * region,HRESULT res)97 static bool grab_region(dsound_t *ds, uint32_t write_ptr,
98 struct audio_lock *region, HRESULT res)
99 {
100 if (res == DSERR_BUFFERLOST)
101 {
102 #ifdef DEBUG
103 RARCH_WARN("[DirectSound error]: %s\n", "DSERR_BUFFERLOST");
104 #endif
105 if ((res = IDirectSoundBuffer_Restore(ds->dsb)) != DS_OK)
106 return false;
107 if ((res = IDirectSoundBuffer_Lock(ds->dsb, write_ptr, CHUNK_SIZE,
108 ®ion->chunk1, ®ion->size1, ®ion->chunk2, ®ion->size2, 0)) != DS_OK)
109 return false;
110 return true;
111 }
112
113 #ifdef DEBUG
114 switch (res)
115 {
116 case DSERR_INVALIDCALL:
117 RARCH_WARN("[DirectSound error]: %s\n", "DSERR_INVALIDCALL");
118 break;
119 case DSERR_INVALIDPARAM:
120 RARCH_WARN("[DirectSound error]: %s\n", "DSERR_INVALIDPARAM");
121 break;
122 case DSERR_PRIOLEVELNEEDED:
123 RARCH_WARN("[DirectSound error]: %s\n", "DSERR_PRIOLEVELNEEDED");
124 break;
125 default:
126 break;
127 }
128 #endif
129
130 return false;
131 }
132
133 #ifdef HAVE_THREADS
dsound_thread(void * data)134 static void dsound_thread(void *data)
135 #else
136 static DWORD CALLBACK dsound_thread(PVOID data)
137 #endif
138 {
139 DWORD write_ptr;
140 dsound_t *ds = (dsound_t*)data;
141
142 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
143
144 IDirectSoundBuffer_GetCurrentPosition(ds->dsb, NULL, &write_ptr);
145 write_ptr = (write_ptr + ds->buffer_size / 2) % ds->buffer_size;
146
147 while (ds->thread_alive)
148 {
149 HRESULT res;
150 bool is_pull = false;
151 struct audio_lock region;
152 DWORD read_ptr, avail, fifo_avail;
153
154 IDirectSoundBuffer_GetCurrentPosition(ds->dsb, &read_ptr, NULL);
155 avail = write_avail(read_ptr, write_ptr, ds->buffer_size);
156
157 EnterCriticalSection(&ds->crit);
158 fifo_avail = FIFO_READ_AVAIL(ds->buffer);
159 LeaveCriticalSection(&ds->crit);
160
161 if (avail < CHUNK_SIZE || ((fifo_avail < CHUNK_SIZE) && (avail < ds->buffer_size / 2)))
162 {
163 /* No space to write, or we don't have data in our fifo,
164 * but we can wait some time before it underruns ... */
165
166 /* We could opt for using the notification interface,
167 * but it is not guaranteed to work, so use high
168 * priority sleeping patterns.
169 */
170 retro_sleep(1);
171 continue;
172 }
173
174 if ((res = IDirectSoundBuffer_Lock(ds->dsb, write_ptr, CHUNK_SIZE,
175 ®ion.chunk1, ®ion.size1, ®ion.chunk2, ®ion.size2, 0)) != DS_OK)
176 {
177 if (!grab_region(ds, write_ptr, ®ion, res))
178 {
179 ds->thread_alive = false;
180 SetEvent(ds->event);
181 break;
182 }
183 }
184
185 if (fifo_avail < CHUNK_SIZE)
186 {
187 /* Got space to write, but nothing in FIFO (underrun),
188 * fill block with silence. */
189 memset(region.chunk1, 0, region.size1);
190 memset(region.chunk2, 0, region.size2);
191 }
192 else
193 {
194 /* All is good. Pull from it and notify FIFO. */
195
196 EnterCriticalSection(&ds->crit);
197 if (region.chunk1)
198 fifo_read(ds->buffer, region.chunk1, region.size1);
199 if (region.chunk2)
200 fifo_read(ds->buffer, region.chunk2, region.size2);
201 LeaveCriticalSection(&ds->crit);
202
203 is_pull = true;
204 }
205
206 IDirectSoundBuffer_Unlock(ds->dsb, region.chunk1,
207 region.size1, region.chunk2, region.size2);
208 write_ptr = (write_ptr + region.size1 + region.size2)
209 % ds->buffer_size;
210
211 if (is_pull)
212 SetEvent(ds->event);
213 }
214
215 ExitThread(0);
216 }
217
dsound_stop_thread(dsound_t * ds)218 static void dsound_stop_thread(dsound_t *ds)
219 {
220 if (!ds->thread)
221 return;
222
223 ds->thread_alive = false;
224
225 #ifdef HAVE_THREADS
226 sthread_join(ds->thread);
227 #else
228 WaitForSingleObject(ds->thread, INFINITE);
229 CloseHandle(ds->thread);
230 #endif
231
232 ds->thread = NULL;
233 }
234
dsound_start_thread(dsound_t * ds)235 static bool dsound_start_thread(dsound_t *ds)
236 {
237 if (!ds->thread)
238 {
239 ds->thread_alive = true;
240
241 #ifdef HAVE_THREADS
242 ds->thread = sthread_create(dsound_thread, ds);
243 #else
244 ds->thread = CreateThread(NULL, 0, dsound_thread, ds, 0, NULL);
245 #endif
246 if (!ds->thread)
247 return false;
248 }
249
250 return true;
251 }
252
dsound_clear_buffer(dsound_t * ds)253 static void dsound_clear_buffer(dsound_t *ds)
254 {
255 DWORD size;
256 void *ptr = NULL;
257
258 IDirectSoundBuffer_SetCurrentPosition(ds->dsb, 0);
259
260 if (IDirectSoundBuffer_Lock(ds->dsb, 0, 0, &ptr, &size,
261 NULL, NULL, DSBLOCK_ENTIREBUFFER) == DS_OK)
262 {
263 memset(ptr, 0, size);
264 IDirectSoundBuffer_Unlock(ds->dsb, ptr, size, NULL, 0);
265 }
266 }
267
dsound_free(void * data)268 static void dsound_free(void *data)
269 {
270 dsound_t *ds = (dsound_t*)data;
271
272 if (!ds)
273 return;
274
275 if (ds->thread)
276 {
277 ds->thread_alive = false;
278 #ifdef HAVE_THREADS
279 sthread_join(ds->thread);
280 #else
281 WaitForSingleObject(ds->thread, INFINITE);
282 CloseHandle(ds->thread);
283 #endif
284 }
285
286 DeleteCriticalSection(&ds->crit);
287
288 if (ds->dsb)
289 {
290 IDirectSoundBuffer_Stop(ds->dsb);
291 IDirectSoundBuffer_Release(ds->dsb);
292 }
293
294 if (ds->ds)
295 IDirectSound_Release(ds->ds);
296
297 if (ds->event)
298 CloseHandle(ds->event);
299
300 if (ds->buffer)
301 fifo_free(ds->buffer);
302
303 free(ds);
304 }
305
enumerate_cb(LPGUID guid,LPCSTR desc,LPCSTR module,LPVOID context)306 static BOOL CALLBACK enumerate_cb(LPGUID guid,
307 LPCSTR desc, LPCSTR module, LPVOID context)
308 {
309 union string_list_elem_attr attr;
310 struct string_list *list = (struct string_list*)context;
311
312 attr.i = 0;
313
314 string_list_append(list, desc, attr);
315
316 if (guid)
317 {
318 unsigned i;
319 LPGUID guid_copy = (LPGUID)malloc(sizeof(GUID) * 1);
320 guid_copy->Data1 = guid->Data1;
321 guid_copy->Data2 = guid->Data2;
322 guid_copy->Data3 = guid->Data3;
323 for (i = 0; i < 8; i++)
324 guid_copy->Data4[i] = guid->Data4[i];
325
326 list->elems[list->size-1].userdata = guid_copy;
327 }
328
329 return TRUE;
330 }
331
dsound_set_wavefmt(WAVEFORMATEX * wfx,unsigned channels,unsigned samplerate)332 static void dsound_set_wavefmt(WAVEFORMATEX *wfx,
333 unsigned channels, unsigned samplerate)
334 {
335 wfx->wFormatTag = WAVE_FORMAT_PCM;
336 wfx->nBlockAlign = channels * sizeof(int16_t);
337 wfx->wBitsPerSample = 16;
338
339 wfx->nChannels = channels;
340 wfx->nSamplesPerSec = samplerate;
341 wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign;
342 wfx->cbSize = 0;
343 }
344
dsound_init(const char * dev,unsigned rate,unsigned latency,unsigned block_frames,unsigned * new_rate)345 static void *dsound_init(const char *dev, unsigned rate, unsigned latency,
346 unsigned block_frames,
347 unsigned *new_rate)
348 {
349 LPGUID selected_device = NULL;
350 WAVEFORMATEX wfx = {0};
351 DSBUFFERDESC bufdesc = {0};
352 int32_t idx_found = -1;
353 struct string_list *list = (struct string_list*)dsound_list_new(NULL);
354 dsound_t *ds = (dsound_t*)calloc(1, sizeof(*ds));
355
356 if (!ds)
357 goto error;
358
359 InitializeCriticalSection(&ds->crit);
360
361 if (dev)
362 {
363 /* Search for device name first */
364 if (list && list->elems)
365 {
366 if (list->elems)
367 {
368 unsigned i;
369 for (i = 0; i < list->size; i++)
370 {
371 if (string_is_equal(dev, list->elems[i].data))
372 {
373 idx_found = i;
374 selected_device = (LPGUID)list->elems[idx_found].userdata;
375 break;
376 }
377 }
378 /* Index was not found yet based on name string,
379 * just assume id is a one-character number index. */
380
381 if (idx_found == -1 && isdigit(dev[0]))
382 {
383 idx_found = strtoul(dev, NULL, 0);
384 RARCH_LOG("[DirectSound]: Fallback, device index is a single number index instead: %d.\n", idx_found);
385
386 if (idx_found != -1)
387 {
388 if (idx_found < (int32_t)list->size)
389 {
390 RARCH_LOG("[DirectSound]: Corresponding name: %s\n", list->elems[idx_found].data);
391 selected_device = (LPGUID)list->elems[idx_found].userdata;
392 }
393 }
394 }
395 }
396 }
397 }
398
399 if (DirectSoundCreate(selected_device, &ds->ds, NULL) != DS_OK)
400 goto error;
401
402 #ifndef _XBOX
403 if (IDirectSound_SetCooperativeLevel(ds->ds, GetDesktopWindow(), DSSCL_PRIORITY) != DS_OK)
404 goto error;
405 #endif
406
407 dsound_set_wavefmt(&wfx, 2, rate);
408
409 ds->buffer_size = (latency * wfx.nAvgBytesPerSec) / 1000;
410 ds->buffer_size /= CHUNK_SIZE;
411 ds->buffer_size *= CHUNK_SIZE;
412 if (ds->buffer_size < 4 * CHUNK_SIZE)
413 ds->buffer_size = 4 * CHUNK_SIZE;
414
415 RARCH_LOG("[DirectSound]: Setting buffer size of %u bytes\n", ds->buffer_size);
416 RARCH_LOG("[DirectSound]: Latency = %u ms\n", (unsigned)((1000 * ds->buffer_size) / wfx.nAvgBytesPerSec));
417
418 bufdesc.dwSize = sizeof(DSBUFFERDESC);
419 bufdesc.dwFlags = 0;
420 #ifndef _XBOX
421 bufdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
422 #endif
423 bufdesc.dwBufferBytes = ds->buffer_size;
424 bufdesc.lpwfxFormat = &wfx;
425
426 ds->event = CreateEvent(NULL, false, false, NULL);
427 if (!ds->event)
428 goto error;
429
430 ds->buffer = fifo_new(4 * 1024);
431 if (!ds->buffer)
432 goto error;
433
434 if (IDirectSound_CreateSoundBuffer(ds->ds, &bufdesc, &ds->dsb, 0) != DS_OK)
435 goto error;
436
437 IDirectSoundBuffer_SetVolume(ds->dsb, DSBVOLUME_MAX);
438 IDirectSoundBuffer_SetCurrentPosition(ds->dsb, 0);
439
440 dsound_clear_buffer(ds);
441
442 if (IDirectSoundBuffer_Play(ds->dsb, 0, 0, DSBPLAY_LOOPING) != DS_OK)
443 goto error;
444
445 if (!dsound_start_thread(ds))
446 goto error;
447
448 string_list_free(list);
449 return ds;
450
451 error:
452 RARCH_ERR("[DirectSound] Error occurred in init.\n");
453 if (list)
454 string_list_free(list);
455 dsound_free(ds);
456 return NULL;
457 }
458
dsound_stop(void * data)459 static bool dsound_stop(void *data)
460 {
461 dsound_t *ds = (dsound_t*)data;
462
463 dsound_stop_thread(ds);
464 ds->is_paused = (IDirectSoundBuffer_Stop(ds->dsb) == DS_OK) ? true : false;
465
466 return (ds->is_paused) ? true : false;
467 }
468
dsound_start(void * data,bool is_shutdown)469 static bool dsound_start(void *data, bool is_shutdown)
470 {
471 dsound_t *ds = (dsound_t*)data;
472
473 dsound_clear_buffer(ds);
474
475 if (!dsound_start_thread(ds))
476 return false;
477
478 ds->is_paused = (IDirectSoundBuffer_Play(
479 ds->dsb, 0, 0, DSBPLAY_LOOPING) == DS_OK) ? false : true;
480 return (ds->is_paused) ? false : true;
481 }
482
dsound_alive(void * data)483 static bool dsound_alive(void *data)
484 {
485 dsound_t *ds = (dsound_t*)data;
486
487 if (!ds)
488 return false;
489 return !ds->is_paused;
490 }
491
dsound_set_nonblock_state(void * data,bool state)492 static void dsound_set_nonblock_state(void *data, bool state)
493 {
494 dsound_t *ds = (dsound_t*)data;
495 if (ds)
496 ds->nonblock = state;
497 }
498
dsound_write(void * data,const void * buf_,size_t size)499 static ssize_t dsound_write(void *data, const void *buf_, size_t size)
500 {
501 size_t written = 0;
502 dsound_t *ds = (dsound_t*)data;
503 const uint8_t *buf = (const uint8_t*)buf_;
504
505 if (!ds->thread_alive)
506 return -1;
507
508 if (ds->nonblock)
509 {
510 if (size > 0)
511 {
512 size_t avail;
513
514 EnterCriticalSection(&ds->crit);
515 avail = FIFO_WRITE_AVAIL(ds->buffer);
516 if (avail > size)
517 avail = size;
518
519 fifo_write(ds->buffer, buf, avail);
520 LeaveCriticalSection(&ds->crit);
521
522 buf += avail;
523 size -= avail;
524 written += avail;
525 }
526 }
527 else
528 {
529 while (size > 0)
530 {
531 size_t avail;
532
533 EnterCriticalSection(&ds->crit);
534 avail = FIFO_WRITE_AVAIL(ds->buffer);
535 if (avail > size)
536 avail = size;
537
538 fifo_write(ds->buffer, buf, avail);
539 LeaveCriticalSection(&ds->crit);
540
541 buf += avail;
542 size -= avail;
543 written += avail;
544
545 if (!ds->thread_alive)
546 break;
547
548 if (avail == 0)
549 if (!(WaitForSingleObject(ds->event, 50) == WAIT_OBJECT_0))
550 return -1;
551 }
552 }
553
554 return written;
555 }
556
dsound_write_avail(void * data)557 static size_t dsound_write_avail(void *data)
558 {
559 size_t avail;
560 dsound_t *ds = (dsound_t*)data;
561
562 EnterCriticalSection(&ds->crit);
563 avail = FIFO_WRITE_AVAIL(ds->buffer);
564 LeaveCriticalSection(&ds->crit);
565 return avail;
566 }
567
dsound_buffer_size(void * data)568 static size_t dsound_buffer_size(void *data) { return 4 * 1024; }
dsound_use_float(void * data)569 static bool dsound_use_float(void *data) { return false; }
570
dsound_list_new(void * u)571 static void *dsound_list_new(void *u)
572 {
573 struct string_list *sl = string_list_new();
574
575 if (!sl)
576 return NULL;
577
578 #ifndef _XBOX
579 #ifdef UNICODE
580 DirectSoundEnumerate((LPDSENUMCALLBACKW)enumerate_cb, sl);
581 #else
582 DirectSoundEnumerate((LPDSENUMCALLBACKA)enumerate_cb, sl);
583 #endif
584 #endif
585
586 return sl;
587 }
588
dsound_device_list_free(void * u,void * slp)589 static void dsound_device_list_free(void *u, void *slp)
590 {
591 struct string_list *sl = (struct string_list*)slp;
592
593 if (sl)
594 string_list_free(sl);
595 }
596
597 audio_driver_t audio_dsound = {
598 dsound_init,
599 dsound_write,
600 dsound_stop,
601 dsound_start,
602 dsound_alive,
603 dsound_set_nonblock_state,
604 dsound_free,
605 dsound_use_float,
606 "dsound",
607 dsound_list_new,
608 dsound_device_list_free,
609 dsound_write_avail,
610 dsound_buffer_size,
611 };
612