2 SDL - Simple DirectMedia Layer
3 Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Sam Lantinga
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
10 This library 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. See the GNU
13 Library General Public License for more details.
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the Free
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 /* Allow access to a raw mixing buffer */
32 #include "SDL_types.h"
33 #include "SDL_error.h"
34 #include "SDL_timer.h"
35 #include "SDL_audio.h"
36 #include "SDL_audio_c.h"
37 #include "SDL_dx5audio.h"
39 /* Define this if you want to use DirectX 6 DirectSoundNotify interface */
40 //#define USE_POSITION_NOTIFY
42 /* DirectX function pointers for audio */
43 HRESULT (WINAPI *DSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
45 /* Audio driver functions */
46 static int DX5_OpenAudio(_THIS, SDL_AudioSpec *spec);
47 static void DX5_ThreadInit(_THIS);
48 static void DX5_WaitAudio_BusyWait(_THIS);
49 #ifdef USE_POSITION_NOTIFY
50 static void DX6_WaitAudio_EventWait(_THIS);
52 static void DX5_PlayAudio(_THIS);
53 static Uint8 *DX5_GetAudioBuf(_THIS);
54 static void DX5_WaitDone(_THIS);
55 static void DX5_CloseAudio(_THIS);
57 /* Audio driver bootstrap functions */
59 static int Audio_Available(void)
64 /* Version check DSOUND.DLL (Is DirectX okay?) */
66 DSoundDLL = LoadLibrary(TEXT("DSOUND.DLL"));
67 if ( DSoundDLL != NULL ) {
68 /* We just use basic DirectSound, we're okay */
70 /* Unfortunately, the sound drivers on NT have
71 higher latencies than the audio buffers used
72 by many SDL applications, so there are gaps
73 in the audio - it sounds terrible. Punt for now.
76 ver.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
78 switch (ver.dwPlatformId) {
79 case VER_PLATFORM_WIN32_NT:
80 if ( ver.dwMajorVersion > 4 ) {
93 /* Now check for DirectX 5 or better - otherwise
94 * we will fail later in DX5_OpenAudio without a chance
95 * to fall back to the DIB driver. */
97 /* DirectSoundCaptureCreate was added in DX5 */
98 if (!GetProcAddress(DSoundDLL, TEXT("DirectSoundCaptureCreate")))
103 FreeLibrary(DSoundDLL);
108 /* Functions for loading the DirectX functions dynamically */
109 static HINSTANCE DSoundDLL = NULL;
111 static void DX5_Unload(void)
113 if ( DSoundDLL != NULL ) {
114 FreeLibrary(DSoundDLL);
119 static int DX5_Load(void)
124 DSoundDLL = LoadLibrary(TEXT("DSOUND.DLL"));
125 if ( DSoundDLL != NULL ) {
126 DSoundCreate = (void *)GetProcAddress(DSoundDLL,
127 TEXT("DirectSoundCreate"));
129 if ( DSoundDLL && DSoundCreate ) {
138 static void Audio_DeleteDevice(SDL_AudioDevice *device)
141 free(device->hidden);
145 static SDL_AudioDevice *Audio_CreateDevice(int devindex)
147 SDL_AudioDevice *this;
150 if ( DX5_Load() < 0 ) {
154 /* Initialize all variables that we clean on shutdown */
155 this = (SDL_AudioDevice *)malloc(sizeof(SDL_AudioDevice));
157 memset(this, 0, (sizeof *this));
158 this->hidden = (struct SDL_PrivateAudioData *)
159 malloc((sizeof *this->hidden));
161 if ( (this == NULL) || (this->hidden == NULL) ) {
168 memset(this->hidden, 0, (sizeof *this->hidden));
170 /* Set the function pointers */
171 this->OpenAudio = DX5_OpenAudio;
172 this->ThreadInit = DX5_ThreadInit;
173 this->WaitAudio = DX5_WaitAudio_BusyWait;
174 this->PlayAudio = DX5_PlayAudio;
175 this->GetAudioBuf = DX5_GetAudioBuf;
176 this->WaitDone = DX5_WaitDone;
177 this->CloseAudio = DX5_CloseAudio;
179 this->free = Audio_DeleteDevice;
184 AudioBootStrap DSOUND_bootstrap = {
185 "dsound", "Win95/98/2000 DirectSound",
186 Audio_Available, Audio_CreateDevice
189 static void SetDSerror(const char *function, int code)
191 static const char *error;
192 static char errbuf[1024];
198 "Unsupported interface\n-- Is DirectX 5.0 or later installed?";
200 case DSERR_ALLOCATED:
201 error = "Audio device in use";
203 case DSERR_BADFORMAT:
204 error = "Unsupported audio format";
206 case DSERR_BUFFERLOST:
207 error = "Mixing buffer was lost";
209 case DSERR_CONTROLUNAVAIL:
210 error = "Control requested is not available";
212 case DSERR_INVALIDCALL:
213 error = "Invalid call for the current state";
215 case DSERR_INVALIDPARAM:
216 error = "Invalid parameter";
219 error = "No audio device found";
221 case DSERR_OUTOFMEMORY:
222 error = "Out of memory";
224 case DSERR_PRIOLEVELNEEDED:
225 error = "Caller doesn't have priority";
227 case DSERR_UNSUPPORTED:
228 error = "Function not supported";
231 sprintf(errbuf, "%s: Unknown DirectSound error: 0x%x",
236 sprintf(errbuf, "%s: %s", function, error);
238 SDL_SetError("%s", errbuf);
242 /* DirectSound needs to be associated with a window */
243 static HWND mainwin = NULL;
245 void DX5_SoundFocus(HWND hwnd)
250 static void DX5_ThreadInit(_THIS)
252 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
255 static void DX5_WaitAudio_BusyWait(_THIS)
261 /* Semi-busy wait, since we have no way of getting play notification
262 on a primary mixing buffer located in hardware (DirectX 5.0)
264 result = IDirectSoundBuffer_GetCurrentPosition(mixbuf, &cursor, &junk);
265 if ( result != DS_OK ) {
266 if ( result == DSERR_BUFFERLOST ) {
267 IDirectSoundBuffer_Restore(mixbuf);
270 SetDSerror("DirectSound GetCurrentPosition", result);
276 while ( cursor == playing ) {
277 /* FIXME: find out how much time is left and sleep that long */
280 /* Try to restore a lost sound buffer */
281 IDirectSoundBuffer_GetStatus(mixbuf, &status);
282 if ( (status&DSBSTATUS_BUFFERLOST) ) {
283 IDirectSoundBuffer_Restore(mixbuf);
284 IDirectSoundBuffer_GetStatus(mixbuf, &status);
285 if ( (status&DSBSTATUS_BUFFERLOST) ) {
289 if ( ! (status&DSBSTATUS_PLAYING) ) {
290 result = IDirectSoundBuffer_Play(mixbuf, 0, 0, DSBPLAY_LOOPING);
291 if ( result == DS_OK ) {
295 SetDSerror("DirectSound Play", result);
300 /* Find out where we are playing */
301 result = IDirectSoundBuffer_GetCurrentPosition(mixbuf,
303 if ( result != DS_OK ) {
304 SetDSerror("DirectSound GetCurrentPosition", result);
311 #ifdef USE_POSITION_NOTIFY
312 static void DX6_WaitAudio_EventWait(_THIS)
317 /* Try to restore a lost sound buffer */
318 IDirectSoundBuffer_GetStatus(mixbuf, &status);
319 if ( (status&DSBSTATUS_BUFFERLOST) ) {
320 IDirectSoundBuffer_Restore(mixbuf);
321 IDirectSoundBuffer_GetStatus(mixbuf, &status);
322 if ( (status&DSBSTATUS_BUFFERLOST) ) {
326 if ( ! (status&DSBSTATUS_PLAYING) ) {
327 result = IDirectSoundBuffer_Play(mixbuf, 0, 0, DSBPLAY_LOOPING);
328 if ( result != DS_OK ) {
330 SetDSerror("DirectSound Play", result);
335 WaitForSingleObject(audio_event, INFINITE);
337 #endif /* USE_POSITION_NOTIFY */
339 static void DX5_PlayAudio(_THIS)
341 /* Unlock the buffer, allowing it to play */
343 IDirectSoundBuffer_Unlock(mixbuf, locked_buf, mixlen, NULL, 0);
348 static Uint8 *DX5_GetAudioBuf(_THIS)
354 /* Figure out which blocks to fill next */
356 result = IDirectSoundBuffer_GetCurrentPosition(mixbuf, &cursor, &junk);
357 if ( result == DSERR_BUFFERLOST ) {
358 IDirectSoundBuffer_Restore(mixbuf);
359 result = IDirectSoundBuffer_GetCurrentPosition(mixbuf,
362 if ( result != DS_OK ) {
363 SetDSerror("DirectSound GetCurrentPosition", result);
368 cursor = (cursor+1)%NUM_BUFFERS;
371 /* Lock the audio buffer */
372 result = IDirectSoundBuffer_Lock(mixbuf, cursor, mixlen,
373 (LPVOID *)&locked_buf, &rawlen, NULL, &junk, 0);
374 if ( result == DSERR_BUFFERLOST ) {
375 IDirectSoundBuffer_Restore(mixbuf);
376 result = IDirectSoundBuffer_Lock(mixbuf, cursor, mixlen,
377 (LPVOID *)&locked_buf, &rawlen, NULL, &junk, 0);
379 if ( result != DS_OK ) {
380 SetDSerror("DirectSound Lock", result);
386 static void DX5_WaitDone(_THIS)
390 /* Wait for the playing chunk to finish */
391 stream = this->GetAudioBuf(this);
392 if ( stream != NULL ) {
393 memset(stream, silence, mixlen);
394 this->PlayAudio(this);
396 this->WaitAudio(this);
398 /* Stop the looping sound buffer */
399 IDirectSoundBuffer_Stop(mixbuf);
402 static void DX5_CloseAudio(_THIS)
404 if ( sound != NULL ) {
405 if ( mixbuf != NULL ) {
406 /* Clean up the audio buffer */
407 IDirectSoundBuffer_Release(mixbuf);
410 if ( audio_event != NULL ) {
411 CloseHandle(audio_event);
414 IDirectSound_Release(sound);
419 #ifdef USE_PRIMARY_BUFFER
420 /* This function tries to create a primary audio buffer, and returns the
421 number of audio chunks available in the created buffer.
423 static int CreatePrimary(LPDIRECTSOUND sndObj, HWND focus,
424 LPDIRECTSOUNDBUFFER *sndbuf, WAVEFORMATEX *wavefmt, Uint32 chunksize)
431 /* Try to set primary mixing privileges */
432 result = IDirectSound_SetCooperativeLevel(sndObj, focus,
434 if ( result != DS_OK ) {
436 SetDSerror("DirectSound SetCooperativeLevel", result);
441 /* Try to create the primary buffer */
442 memset(&format, 0, sizeof(format));
443 format.dwSize = sizeof(format);
444 format.dwFlags=(DSBCAPS_PRIMARYBUFFER|DSBCAPS_GETCURRENTPOSITION2);
445 format.dwFlags |= DSBCAPS_STICKYFOCUS;
446 #ifdef USE_POSITION_NOTIFY
447 format.dwFlags |= DSBCAPS_CTRLPOSITIONNOTIFY;
449 result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
450 if ( result != DS_OK ) {
452 SetDSerror("DirectSound CreateSoundBuffer", result);
457 /* Check the size of the fragment buffer */
458 memset(&caps, 0, sizeof(caps));
459 caps.dwSize = sizeof(caps);
460 result = IDirectSoundBuffer_GetCaps(*sndbuf, &caps);
461 if ( result != DS_OK ) {
463 SetDSerror("DirectSound GetCaps", result);
465 IDirectSoundBuffer_Release(*sndbuf);
468 if ( (chunksize > caps.dwBufferBytes) ||
469 ((caps.dwBufferBytes%chunksize) != 0) ) {
470 /* The primary buffer size is not a multiple of 'chunksize'
471 -- this hopefully doesn't happen when 'chunksize' is a
474 IDirectSoundBuffer_Release(*sndbuf);
476 "Primary buffer size is: %d, cannot break it into chunks of %d bytes\n",
477 caps.dwBufferBytes, chunksize);
480 numchunks = (caps.dwBufferBytes/chunksize);
482 /* Set the primary audio format */
483 result = IDirectSoundBuffer_SetFormat(*sndbuf, wavefmt);
484 if ( result != DS_OK ) {
486 SetDSerror("DirectSound SetFormat", result);
488 IDirectSoundBuffer_Release(*sndbuf);
493 #endif /* USE_PRIMARY_BUFFER */
495 /* This function tries to create a secondary audio buffer, and returns the
496 number of audio chunks available in the created buffer.
498 static int CreateSecondary(LPDIRECTSOUND sndObj, HWND focus,
499 LPDIRECTSOUNDBUFFER *sndbuf, WAVEFORMATEX *wavefmt, Uint32 chunksize)
501 const int numchunks = 2;
504 LPVOID pvAudioPtr1, pvAudioPtr2;
505 DWORD dwAudioBytes1, dwAudioBytes2;
507 /* Try to set primary mixing privileges */
509 result = IDirectSound_SetCooperativeLevel(sndObj,
510 focus, DSSCL_PRIORITY);
512 result = IDirectSound_SetCooperativeLevel(sndObj,
513 GetDesktopWindow(), DSSCL_NORMAL);
515 if ( result != DS_OK ) {
517 SetDSerror("DirectSound SetCooperativeLevel", result);
522 /* Try to create the secondary buffer */
523 memset(&format, 0, sizeof(format));
524 format.dwSize = sizeof(format);
525 format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
526 #ifdef USE_POSITION_NOTIFY
527 format.dwFlags |= DSBCAPS_CTRLPOSITIONNOTIFY;
530 format.dwFlags |= DSBCAPS_GLOBALFOCUS;
532 format.dwFlags |= DSBCAPS_STICKYFOCUS;
534 format.dwBufferBytes = numchunks*chunksize;
535 if ( (format.dwBufferBytes < DSBSIZE_MIN) ||
536 (format.dwBufferBytes > DSBSIZE_MAX) ) {
537 SDL_SetError("Sound buffer size must be between %d and %d",
538 DSBSIZE_MIN/numchunks, DSBSIZE_MAX/numchunks);
541 format.dwReserved = 0;
542 format.lpwfxFormat = wavefmt;
543 result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
544 if ( result != DS_OK ) {
545 SetDSerror("DirectSound CreateSoundBuffer", result);
548 IDirectSoundBuffer_SetFormat(*sndbuf, wavefmt);
550 /* Silence the initial audio buffer */
551 result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
552 (LPVOID *)&pvAudioPtr1, &dwAudioBytes1,
553 (LPVOID *)&pvAudioPtr2, &dwAudioBytes2,
554 DSBLOCK_ENTIREBUFFER);
555 if ( result == DS_OK ) {
556 if ( wavefmt->wBitsPerSample == 8 ) {
557 memset(pvAudioPtr1, 0x80, dwAudioBytes1);
559 memset(pvAudioPtr1, 0x00, dwAudioBytes1);
561 IDirectSoundBuffer_Unlock(*sndbuf,
562 (LPVOID)pvAudioPtr1, dwAudioBytes1,
563 (LPVOID)pvAudioPtr2, dwAudioBytes2);
566 /* We're ready to go */
570 /* This function tries to set position notify events on the mixing buffer */
571 #ifdef USE_POSITION_NOTIFY
572 static int CreateAudioEvent(_THIS)
574 LPDIRECTSOUNDNOTIFY notify;
575 DSBPOSITIONNOTIFY *notify_positions;
579 /* Default to fail on exit */
583 /* Query for the interface */
584 result = IDirectSoundBuffer_QueryInterface(mixbuf,
585 &IID_IDirectSoundNotify, (void *)¬ify);
586 if ( result != DS_OK ) {
590 /* Allocate the notify structures */
591 notify_positions = (DSBPOSITIONNOTIFY *)malloc(NUM_BUFFERS*
592 sizeof(*notify_positions));
593 if ( notify_positions == NULL ) {
597 /* Create the notify event */
598 audio_event = CreateEvent(NULL, FALSE, FALSE, NULL);
599 if ( audio_event == NULL ) {
603 /* Set up the notify structures */
604 for ( i=0; i<NUM_BUFFERS; ++i ) {
605 notify_positions[i].dwOffset = i*mixlen;
606 notify_positions[i].hEventNotify = audio_event;
608 result = IDirectSoundNotify_SetNotificationPositions(notify,
609 NUM_BUFFERS, notify_positions);
610 if ( result == DS_OK ) {
614 if ( notify != NULL ) {
615 IDirectSoundNotify_Release(notify);
619 #endif /* USE_POSITION_NOTIFY */
621 static int DX5_OpenAudio(_THIS, SDL_AudioSpec *spec)
624 WAVEFORMATEX waveformat;
626 /* Set basic WAVE format parameters */
627 memset(&waveformat, 0, sizeof(waveformat));
628 waveformat.wFormatTag = WAVE_FORMAT_PCM;
630 /* Determine the audio parameters from the AudioSpec */
631 switch ( spec->format & 0xFF ) {
633 /* Unsigned 8 bit audio data */
634 spec->format = AUDIO_U8;
636 waveformat.wBitsPerSample = 8;
639 /* Signed 16 bit audio data */
640 spec->format = AUDIO_S16;
642 waveformat.wBitsPerSample = 16;
645 SDL_SetError("Unsupported audio format");
648 waveformat.nChannels = spec->channels;
649 waveformat.nSamplesPerSec = spec->freq;
650 waveformat.nBlockAlign =
651 waveformat.nChannels * (waveformat.wBitsPerSample/8);
652 waveformat.nAvgBytesPerSec =
653 waveformat.nSamplesPerSec * waveformat.nBlockAlign;
655 /* Update the fragment size as size in bytes */
656 SDL_CalculateAudioSpec(spec);
658 /* Open the audio device */
659 result = DSoundCreate(NULL, &sound, NULL);
660 if ( result != DS_OK ) {
661 SetDSerror("DirectSoundCreate", result);
665 /* Create the audio buffer to which we write */
667 #ifdef USE_PRIMARY_BUFFER
669 NUM_BUFFERS = CreatePrimary(sound, mainwin, &mixbuf,
670 &waveformat, spec->size);
672 #endif /* USE_PRIMARY_BUFFER */
673 if ( NUM_BUFFERS < 0 ) {
674 NUM_BUFFERS = CreateSecondary(sound, mainwin, &mixbuf,
675 &waveformat, spec->size);
676 if ( NUM_BUFFERS < 0 ) {
680 fprintf(stderr, "Using secondary audio buffer\n");
685 fprintf(stderr, "Using primary audio buffer\n");
688 /* The buffer will auto-start playing in DX5_WaitAudio() */
692 #ifdef USE_POSITION_NOTIFY
693 /* See if we can use DirectX 6 event notification */
694 if ( CreateAudioEvent(this) == 0 ) {
695 this->WaitAudio = DX6_WaitAudio_EventWait;
697 this->WaitAudio = DX5_WaitAudio_BusyWait;