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 }