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                   &region->chunk1, &region->size1, &region->chunk2, &region->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                   &region.chunk1, &region.size1, &region.chunk2, &region.size2, 0)) != DS_OK)
176       {
177          if (!grab_region(ds, write_ptr, &region, 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