/ source / audiolib / src / driver_directsound.cpp
driver_directsound.cpp
  1  /*
  2   Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
  3   Copyright (C) EDuke32 developers and contributors
  4  
  5   This program is free software; you can redistribute it and/or
  6   modify it under the terms of the GNU General Public License
  7   as published by the Free Software Foundation; either version 2
  8   of the License, or (at your option) any later version.
  9  
 10   This program is distributed in the hope that it will be useful,
 11   but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 13  
 14   See the GNU General Public License for more details.
 15  
 16   You should have received a copy of the GNU General Public License
 17   along with this program; if not, write to the Free Software
 18   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 19  
 20   */
 21  
 22  /**
 23   * DirectSound output driver for MultiVoc
 24   */
 25  
 26  #define NEED_MMSYSTEM_H
 27  #define NEED_DSOUND_H
 28  
 29  #include "driver_directsound.h"
 30  
 31  #include "baselayer.h"
 32  #include "multivoc.h"
 33  #include "mutex.h"
 34  #include "windows_inc.h"
 35  #include "winbits.h"
 36  
 37  #define MIXBUFFERPOSITIONS 8
 38  
 39  static int ErrorCode;
 40  static int Initialised;
 41  static int Playing;
 42  
 43  static char *  MixBuffer;
 44  static int MixBufferSize;
 45  static int MixBufferCount;
 46  static int MixBufferCurrent;
 47  static int MixBufferUsed;
 48  
 49  static void (*MixCallBack)(void);
 50  
 51  static LPDIRECTSOUND lpds;
 52  static LPDIRECTSOUNDBUFFER lpdsbprimary, lpdsbsec;
 53  static LPDIRECTSOUNDNOTIFY lpdsnotify;
 54  
 55  static HANDLE mixThread;
 56  static mutex_t mutex;
 57  
 58  static DSBPOSITIONNOTIFY notifyPositions[MIXBUFFERPOSITIONS + 1] = {};
 59  
 60  static void FillBufferPosition(char * ptr, int remaining)
 61  {
 62      int len = 0;
 63  
 64      do
 65      {
 66          if (MixBufferUsed == MixBufferSize)
 67          {
 68              MixCallBack();
 69              MixBufferUsed = 0;
 70  
 71              if (++MixBufferCurrent >= MixBufferCount)
 72                  MixBufferCurrent -= MixBufferCount;
 73          }
 74  
 75          do
 76          {
 77              char *sptr = MixBuffer + (MixBufferCurrent * MixBufferSize) + MixBufferUsed;
 78  
 79              len = MixBufferSize - MixBufferUsed;
 80  
 81              if (remaining < len)
 82                  len = remaining;
 83  
 84              memcpy(ptr, sptr, len);
 85  
 86              ptr += len;
 87              MixBufferUsed += len;
 88              remaining -= len;
 89          }
 90          while (remaining >= len && MixBufferUsed < MixBufferSize);
 91      }
 92      while (remaining >= len);
 93  }
 94  
 95  static void FillBuffer(int bufnum)
 96  {
 97      LPVOID ptr, ptr2;
 98      DWORD remaining, remaining2;
 99      int retries = 1;
100  
101      do
102      {
103          HRESULT err = IDirectSoundBuffer_Lock(lpdsbsec, notifyPositions[bufnum].dwOffset, notifyPositions[1].dwOffset,
104                                                &ptr, &remaining, &ptr2, &remaining2, 0);
105  
106          if (EDUKE32_PREDICT_FALSE(FAILED(err)))
107          {
108              if (err == DSERR_BUFFERLOST)
109              {
110                  if (FAILED(err = IDirectSoundBuffer_Restore(lpdsbsec)))
111                      goto fail;
112  
113                  if (retries-- > 0)
114                      continue;
115              }
116  fail:
117              LOG_F(ERROR, "Unable to lock DirectSound buffer: IDirectSoundBuffer_Lock error %x", (uint32_t)err);
118  
119              return;
120          }
121          break;
122      }
123      while (1);
124  
125      if (ptr && remaining)
126          FillBufferPosition((char *)ptr, remaining);
127  
128      if (ptr2 && remaining2)
129          FillBufferPosition((char *)ptr2, remaining2);
130  
131      IDirectSoundBuffer_Unlock(lpdsbsec, ptr, remaining, ptr2, remaining2);
132  }
133  
134  static unsigned WINAPI fillDataThread(LPVOID lpParameter)
135  {
136      UNREFERENCED_PARAMETER(lpParameter);
137  
138      debugThreadName("DSound_fillData");
139  
140      HANDLE handles[MIXBUFFERPOSITIONS+1];
141  
142      for (int i = 0; i < ARRAY_SSIZE(handles); i++)
143          handles[i] = notifyPositions[i].hEventNotify;
144  
145      do
146      {
147          DWORD const waitret = WaitForMultipleObjects(MIXBUFFERPOSITIONS, handles, FALSE, INFINITE);
148  
149          if (waitret >= WAIT_OBJECT_0 && waitret < WAIT_OBJECT_0+MIXBUFFERPOSITIONS)
150          {
151              mutex_lock(&mutex);
152              FillBuffer((waitret + MIXBUFFERPOSITIONS - 1 - WAIT_OBJECT_0) % MIXBUFFERPOSITIONS);
153              mutex_unlock(&mutex);
154              SwitchToThread(); // this signals the OS to context switch
155          }
156          else
157          {
158              switch (waitret)
159              {
160                  case WAIT_OBJECT_0 + MIXBUFFERPOSITIONS:
161                      return 0;
162                  case WAIT_FAILED:
163                      {
164                          auto err = GetLastError();
165                          LOG_F(ERROR, "Unable to fill DirectSound buffer: WaitForMultipleObjects failed: %s", windowsGetErrorMessage(err));
166                      }
167                      break;
168                  default:
169                      LOG_F(ERROR, "Unable to fill DirectSound buffer: WaitForMultipleObjects returned %d", (int)waitret);
170                      break;
171              }
172          }
173      }
174      while (1);
175  
176      return 0;
177  }
178  
179  static void TeardownDSound(HRESULT err)
180  {
181      if (FAILED(err))
182          LOG_F(ERROR, "DirectSound error: %x", (uint32_t)err);
183  
184      if (lpdsnotify)
185          IDirectSoundNotify_Release(lpdsnotify), lpdsnotify = nullptr;
186  
187      for (int i = 0; i < MIXBUFFERPOSITIONS + 1; i++)
188      {
189          if (notifyPositions[i].hEventNotify)
190              CloseHandle(notifyPositions[i].hEventNotify);
191          notifyPositions[i].hEventNotify = 0;
192      }
193  
194  #ifdef RENDERTYPEWIN
195      mutex_destroy(&mutex);
196  #endif
197  
198      if (lpdsbsec)
199          IDirectSoundBuffer_Release(lpdsbsec), lpdsbsec = nullptr;
200  
201      if (lpdsbprimary)
202          IDirectSoundBuffer_Release(lpdsbprimary), lpdsbprimary = nullptr;
203  
204      if (lpds)
205          IDirectSound_Release(lpds), lpds = nullptr;
206  }
207  
208  static int DirectSound_Error(HRESULT err, int code)
209  {
210      TeardownDSound(err);
211      ErrorCode = code;
212      return DSErr_Error;
213  }
214  
215  int DirectSoundDrv_PCM_Init(int *mixrate, int *numchannels, void * initdata)
216  {
217      HRESULT err;
218      DSBUFFERDESC bufdesc = {};
219      WAVEFORMATEX wfex    = {};
220  
221      if (Initialised)
222          DirectSoundDrv_PCM_Shutdown();
223  
224      if (FAILED(err = DirectSoundCreate(0, &lpds, 0)))
225          return DirectSound_Error(err, DSErr_DirectSoundCreate);
226  
227      if (FAILED(err = IDirectSound_SetCooperativeLevel(lpds, (HWND) initdata, DSSCL_PRIORITY)))
228          return DirectSound_Error(err, DSErr_SetCooperativeLevel);
229  
230      bufdesc.dwSize = sizeof(DSBUFFERDESC);
231      bufdesc.dwFlags = DSBCAPS_LOCSOFTWARE | DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS;
232  
233      if (FAILED(err = IDirectSound_CreateSoundBuffer(lpds, &bufdesc, &lpdsbprimary, 0)))
234          return DirectSound_Error(err, DSErr_CreateSoundBuffer);
235  
236      wfex.wFormatTag      = WAVE_FORMAT_PCM;
237      wfex.nChannels       = *numchannels;
238      wfex.nSamplesPerSec  = *mixrate;
239      wfex.wBitsPerSample  = 16;
240      wfex.nBlockAlign     = wfex.nChannels * wfex.wBitsPerSample / 8;
241      wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign;
242  
243      if (FAILED(err = IDirectSoundBuffer_SetFormat(lpdsbprimary, &wfex)))
244          return DirectSound_Error(err, DSErr_SetFormat);
245  
246      bufdesc.dwFlags = DSBCAPS_LOCSOFTWARE | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS;
247  
248      bufdesc.dwBufferBytes = wfex.nBlockAlign * 2048 * 2;
249      bufdesc.lpwfxFormat = &wfex;
250  
251      if (FAILED(err = IDirectSound_CreateSoundBuffer(lpds, &bufdesc, &lpdsbsec, 0)))
252          return DirectSound_Error(err, DSErr_CreateSoundBufferSecondary);
253  
254      if (FAILED(err = IDirectSoundBuffer_QueryInterface(lpdsbsec, &IID_IDirectSoundNotify, (LPVOID *)&lpdsnotify)))
255          return DirectSound_Error(err, DSErr_Notify);
256  
257      for (int i = 0; i < MIXBUFFERPOSITIONS; i++)
258      {
259          notifyPositions[i].dwOffset = (bufdesc.dwBufferBytes/MIXBUFFERPOSITIONS)*i;
260          notifyPositions[i].hEventNotify = CreateEvent(nullptr, FALSE, FALSE, nullptr);
261          if (!notifyPositions[i].hEventNotify)
262              return DirectSound_Error(DS_OK, DSErr_NotifyEvents);
263      }
264  
265      notifyPositions[MIXBUFFERPOSITIONS].dwOffset = DSBPN_OFFSETSTOP;
266      notifyPositions[MIXBUFFERPOSITIONS].hEventNotify = CreateEvent(nullptr, FALSE, FALSE, nullptr);
267  
268      if (FAILED(err = IDirectSoundNotify_SetNotificationPositions(lpdsnotify, MIXBUFFERPOSITIONS+1, notifyPositions)))
269          return DirectSound_Error(err, DSErr_SetNotificationPositions);
270  
271      if (FAILED(err = IDirectSoundBuffer_Play(lpdsbprimary, 0, 0, DSBPLAY_LOOPING)))
272          return DirectSound_Error(err, DSErr_Play);
273  
274      mutex_init(&mutex);
275  
276      Initialised = 1;
277  
278      return DSErr_Ok;
279  }
280  
281  void DirectSoundDrv_PCM_Shutdown(void)
282  {
283      if (!Initialised)
284          return;
285  
286      DirectSoundDrv_PCM_StopPlayback();
287      TeardownDSound(DS_OK);
288  
289      Initialised = 0;
290  }
291  
292  int DirectSoundDrv_PCM_BeginPlayback(char *BufferStart, int BufferSize, int NumDivisions, void (*CallBackFunc)(void))
293  {
294      if (!Initialised)
295      {
296          ErrorCode = DSErr_Uninitialised;
297          return DSErr_Error;
298      }
299  
300      DirectSoundDrv_PCM_StopPlayback();
301  
302      MixBuffer        = BufferStart;
303      MixBufferSize    = BufferSize;
304      MixBufferCount   = NumDivisions;
305      MixBufferCurrent = 0;
306      MixBufferUsed    = 0;
307      MixCallBack      = CallBackFunc;
308  
309      // prime the buffer
310      FillBuffer(0);
311  
312      if ((mixThread = (HANDLE)_beginthreadex(nullptr, 0, fillDataThread, 0, 0, 0)) == nullptr)
313      {
314          ErrorCode = DSErr_CreateThread;
315          return DSErr_Error;
316      }
317  
318      SetThreadPriority(mixThread, THREAD_PRIORITY_ABOVE_NORMAL);
319  
320      HRESULT err = IDirectSoundBuffer_Play(lpdsbsec, 0, 0, DSBPLAY_LOOPING);
321  
322      if (FAILED(err))
323      {
324          ErrorCode = DSErr_PlaySecondary;
325          return DSErr_Error;
326      }
327  
328      Playing = 1;
329  
330      return DSErr_Ok;
331  }
332  
333  void DirectSoundDrv_PCM_StopPlayback(void)
334  {
335      if (!Playing)
336          return;
337  
338      IDirectSoundBuffer_Stop(lpdsbsec);
339      IDirectSoundBuffer_SetCurrentPosition(lpdsbsec, 0);
340  
341      if (mixThread)
342          CloseHandle(mixThread);
343  
344      mixThread = 0;
345      Playing = 0;
346  }
347  
348  void DirectSoundDrv_PCM_Lock(void)   { mutex_lock(&mutex); }
349  void DirectSoundDrv_PCM_Unlock(void) { mutex_unlock(&mutex); }
350  int DirectSoundDrv_GetError(void)    { return ErrorCode; }
351  
352  const char *DirectSoundDrv_ErrorString(int ErrorNumber)
353  {
354      switch (ErrorNumber)
355      {
356          case DSErr_Error:                      return DirectSoundDrv_ErrorString(ErrorCode);
357          case DSErr_Ok:                         return "DirectSound ok.";
358          case DSErr_Uninitialised:              return "DirectSound uninitialized.";
359          case DSErr_DirectSoundCreate:          return "DirectSound error: DirectSoundCreate failed.";
360          case DSErr_SetCooperativeLevel:        return "DirectSound error: SetCooperativeLevel failed.";
361          case DSErr_CreateSoundBuffer:          return "DirectSound error: primary CreateSoundBuffer failed.";
362          case DSErr_CreateSoundBufferSecondary: return "DirectSound error: secondary CreateSoundBuffer failed.";
363          case DSErr_SetFormat:                  return "DirectSound error: primary buffer SetFormat failed.";
364          case DSErr_SetFormatSecondary:         return "DirectSound error: secondary buffer SetFormat failed.";
365          case DSErr_Notify:                     return "DirectSound error: failed querying secondary buffer for notify interface.";
366          case DSErr_NotifyEvents:               return "DirectSound error: failed creating notify events.";
367          case DSErr_SetNotificationPositions:   return "DirectSound error: failed setting notification positions.";
368          case DSErr_Play:                       return "DirectSound error: primary buffer Play failed.";
369          case DSErr_PlaySecondary:              return "DirectSound error: secondary buffer Play failed.";
370          case DSErr_CreateThread:               return "DirectSound error: failed creating mix thread.";
371          default:                               return "Unknown DirectSound error code.";
372      }
373  }