| /* |
| SDL - Simple DirectMedia Layer |
| Copyright (C) 1997-2006 Sam Lantinga |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| This library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with this library; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| |
| Sam Lantinga |
| slouken@libsdl.org |
| */ |
| #include "SDL_config.h" |
| |
| /* Allow access to a raw mixing buffer */ |
| |
| #include "SDL_timer.h" |
| #include "SDL_audio.h" |
| #include "../SDL_audio_c.h" |
| #include "SDL_dx5audio.h" |
| |
| /* !!! FIXME: move this somewhere that other drivers can use it... */ |
| #if defined(_WIN32_WCE) |
| #define WINDOWS_OS_NAME "Windows CE/PocketPC" |
| #elif defined(WIN64) |
| #define WINDOWS_OS_NAME "Win64" |
| #else |
| #define WINDOWS_OS_NAME "Win32" |
| #endif |
| |
| /* DirectX function pointers for audio */ |
| static HINSTANCE DSoundDLL = NULL; |
| static HRESULT (WINAPI *DSoundCreate)(LPGUID,LPDIRECTSOUND*,LPUNKNOWN) = NULL; |
| |
| static void |
| DSOUND_Unload(void) |
| { |
| if (DSoundDLL != NULL) { |
| FreeLibrary(DSoundDLL); |
| } |
| |
| DSoundCreate = NULL; |
| DSoundDLL = NULL; |
| } |
| |
| |
| static int |
| DSOUND_Load(void) |
| { |
| int loaded = 0; |
| |
| DSOUND_Unload(); |
| |
| DSoundDLL = LoadLibrary(TEXT("DSOUND.DLL")); |
| if (DSoundDLL == NULL) { |
| SDL_SetError("DirectSound: failed to load DSOUND.DLL"); |
| } else { |
| /* Now make sure we have DirectX 5 or better... */ |
| /* (DirectSoundCaptureCreate was added in DX5) */ |
| if (!GetProcAddress(DSoundDLL, TEXT("DirectSoundCaptureCreate"))) { |
| SDL_SetError("DirectSound: System doesn't appear to have DX5."); |
| } else { |
| DSoundCreate = (void *) GetProcAddress(DSoundDLL, |
| TEXT("DirectSoundCreate")); |
| } |
| |
| if (!DSoundCreate) { |
| SDL_SetError("DirectSound: Failed to find DirectSoundCreate"); |
| } else { |
| loaded = 1; |
| } |
| } |
| |
| if (!loaded) { |
| DSOUND_Unload(); |
| } |
| |
| return loaded; |
| } |
| |
| |
| static void |
| SetDSerror(const char *function, int code) |
| { |
| static const char *error; |
| static char errbuf[1024]; |
| |
| errbuf[0] = 0; |
| switch (code) { |
| case E_NOINTERFACE: |
| error = "Unsupported interface -- Is DirectX 5.0 or later installed?"; |
| break; |
| case DSERR_ALLOCATED: |
| error = "Audio device in use"; |
| break; |
| case DSERR_BADFORMAT: |
| error = "Unsupported audio format"; |
| break; |
| case DSERR_BUFFERLOST: |
| error = "Mixing buffer was lost"; |
| break; |
| case DSERR_CONTROLUNAVAIL: |
| error = "Control requested is not available"; |
| break; |
| case DSERR_INVALIDCALL: |
| error = "Invalid call for the current state"; |
| break; |
| case DSERR_INVALIDPARAM: |
| error = "Invalid parameter"; |
| break; |
| case DSERR_NODRIVER: |
| error = "No audio device found"; |
| break; |
| case DSERR_OUTOFMEMORY: |
| error = "Out of memory"; |
| break; |
| case DSERR_PRIOLEVELNEEDED: |
| error = "Caller doesn't have priority"; |
| break; |
| case DSERR_UNSUPPORTED: |
| error = "Function not supported"; |
| break; |
| default: |
| SDL_snprintf(errbuf, SDL_arraysize(errbuf), |
| "%s: Unknown DirectSound error: 0x%x", function, code); |
| break; |
| } |
| if (!errbuf[0]) { |
| SDL_snprintf(errbuf, SDL_arraysize(errbuf), "%s: %s", function, |
| error); |
| } |
| SDL_SetError("%s", errbuf); |
| return; |
| } |
| |
| /* DirectSound needs to be associated with a window */ |
| static HWND mainwin = NULL; |
| /* */ |
| |
| void |
| DSOUND_SoundFocus(HWND hwnd) |
| { |
| /* !!! FIXME: probably broken with multi-window support in SDL 1.3 ... */ |
| mainwin = hwnd; |
| } |
| |
| static void |
| DSOUND_ThreadInit(_THIS) |
| { |
| SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); |
| } |
| |
| static void |
| DSOUND_WaitDevice(_THIS) |
| { |
| DWORD status = 0; |
| DWORD cursor = 0; |
| DWORD junk = 0; |
| HRESULT result = DS_OK; |
| |
| /* Semi-busy wait, since we have no way of getting play notification |
| on a primary mixing buffer located in hardware (DirectX 5.0) |
| */ |
| result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf, |
| &junk, &cursor); |
| if (result != DS_OK) { |
| if (result == DSERR_BUFFERLOST) { |
| IDirectSoundBuffer_Restore(this->hidden->mixbuf); |
| } |
| #ifdef DEBUG_SOUND |
| SetDSerror("DirectSound GetCurrentPosition", result); |
| #endif |
| return; |
| } |
| |
| while ((cursor / this->hidden->mixlen) == this->hidden->lastchunk) { |
| /* FIXME: find out how much time is left and sleep that long */ |
| SDL_Delay(1); |
| |
| /* Try to restore a lost sound buffer */ |
| IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status); |
| if ((status & DSBSTATUS_BUFFERLOST)) { |
| IDirectSoundBuffer_Restore(this->hidden->mixbuf); |
| IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status); |
| if ((status & DSBSTATUS_BUFFERLOST)) { |
| break; |
| } |
| } |
| if (!(status & DSBSTATUS_PLAYING)) { |
| result = IDirectSoundBuffer_Play(this->hidden->mixbuf, 0, 0, |
| DSBPLAY_LOOPING); |
| if (result == DS_OK) { |
| continue; |
| } |
| #ifdef DEBUG_SOUND |
| SetDSerror("DirectSound Play", result); |
| #endif |
| return; |
| } |
| |
| /* Find out where we are playing */ |
| result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf, |
| &junk, &cursor); |
| if (result != DS_OK) { |
| SetDSerror("DirectSound GetCurrentPosition", result); |
| return; |
| } |
| } |
| } |
| |
| static void |
| DSOUND_PlayDevice(_THIS) |
| { |
| /* Unlock the buffer, allowing it to play */ |
| if (this->hidden->locked_buf) { |
| IDirectSoundBuffer_Unlock(this->hidden->mixbuf, |
| this->hidden->locked_buf, |
| this->hidden->mixlen, NULL, 0); |
| } |
| |
| } |
| |
| static Uint8 * |
| DSOUND_GetDeviceBuf(_THIS) |
| { |
| DWORD cursor = 0; |
| DWORD junk = 0; |
| HRESULT result = DS_OK; |
| DWORD rawlen = 0; |
| |
| /* Figure out which blocks to fill next */ |
| this->hidden->locked_buf = NULL; |
| result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf, |
| &junk, &cursor); |
| if (result == DSERR_BUFFERLOST) { |
| IDirectSoundBuffer_Restore(this->hidden->mixbuf); |
| result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf, |
| &junk, &cursor); |
| } |
| if (result != DS_OK) { |
| SetDSerror("DirectSound GetCurrentPosition", result); |
| return (NULL); |
| } |
| cursor /= this->hidden->mixlen; |
| #ifdef DEBUG_SOUND |
| /* Detect audio dropouts */ |
| { |
| DWORD spot = cursor; |
| if (spot < this->hidden->lastchunk) { |
| spot += this->hidden->num_buffers; |
| } |
| if (spot > this->hidden->lastchunk + 1) { |
| fprintf(stderr, "Audio dropout, missed %d fragments\n", |
| (spot - (this->hidden->lastchunk + 1))); |
| } |
| } |
| #endif |
| this->hidden->lastchunk = cursor; |
| cursor = (cursor + 1) % this->hidden->num_buffers; |
| cursor *= this->hidden->mixlen; |
| |
| /* Lock the audio buffer */ |
| result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor, |
| this->hidden->mixlen, |
| (LPVOID *) &this->hidden->locked_buf, |
| &rawlen, NULL, &junk, 0); |
| if (result == DSERR_BUFFERLOST) { |
| IDirectSoundBuffer_Restore(this->hidden->mixbuf); |
| result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor, |
| this->hidden->mixlen, |
| (LPVOID *) &this->hidden->locked_buf, |
| &rawlen, NULL, &junk, 0); |
| } |
| if (result != DS_OK) { |
| SetDSerror("DirectSound Lock", result); |
| return (NULL); |
| } |
| return (this->hidden->locked_buf); |
| } |
| |
| static void |
| DSOUND_WaitDone(_THIS) |
| { |
| Uint8 *stream = DSOUND_GetDeviceBuf(this); |
| |
| /* Wait for the playing chunk to finish */ |
| if (stream != NULL) { |
| SDL_memset(stream, this->spec.silence, this->hidden->mixlen); |
| DSOUND_PlayDevice(this); |
| } |
| DSOUND_WaitDevice(this); |
| |
| /* Stop the looping sound buffer */ |
| IDirectSoundBuffer_Stop(this->hidden->mixbuf); |
| } |
| |
| static void |
| DSOUND_CloseDevice(_THIS) |
| { |
| if (this->hidden != NULL) { |
| if (this->hidden->sound != NULL) { |
| if (this->hidden->mixbuf != NULL) { |
| /* Clean up the audio buffer */ |
| IDirectSoundBuffer_Release(this->hidden->mixbuf); |
| this->hidden->mixbuf = NULL; |
| } |
| IDirectSound_Release(this->hidden->sound); |
| this->hidden->sound = NULL; |
| } |
| |
| SDL_free(this->hidden); |
| this->hidden = NULL; |
| } |
| } |
| |
| /* This function tries to create a secondary audio buffer, and returns the |
| number of audio chunks available in the created buffer. |
| */ |
| static int |
| CreateSecondary(_THIS, HWND focus, WAVEFORMATEX *wavefmt) |
| { |
| LPDIRECTSOUND sndObj = this->hidden->sound; |
| LPDIRECTSOUNDBUFFER *sndbuf = this->hidden->mixbuf; |
| Uint32 chunksize = this->spec.size; |
| const int numchunks = 8; |
| HRESULT result = DS_OK; |
| DSBUFFERDESC format; |
| LPVOID pvAudioPtr1, pvAudioPtr2; |
| DWORD dwAudioBytes1, dwAudioBytes2; |
| |
| /* Try to set primary mixing privileges */ |
| if (focus) { |
| result = IDirectSound_SetCooperativeLevel(sndObj, |
| focus, DSSCL_PRIORITY); |
| } else { |
| result = IDirectSound_SetCooperativeLevel(sndObj, |
| GetDesktopWindow(), |
| DSSCL_NORMAL); |
| } |
| if (result != DS_OK) { |
| SetDSerror("DirectSound SetCooperativeLevel", result); |
| return (-1); |
| } |
| |
| /* Try to create the secondary buffer */ |
| SDL_memset(&format, 0, sizeof(format)); |
| format.dwSize = sizeof(format); |
| format.dwFlags = DSBCAPS_GETCURRENTPOSITION2; |
| if (!focus) { |
| format.dwFlags |= DSBCAPS_GLOBALFOCUS; |
| } else { |
| format.dwFlags |= DSBCAPS_STICKYFOCUS; |
| } |
| format.dwBufferBytes = numchunks * chunksize; |
| if ((format.dwBufferBytes < DSBSIZE_MIN) || |
| (format.dwBufferBytes > DSBSIZE_MAX)) { |
| SDL_SetError("Sound buffer size must be between %d and %d", |
| DSBSIZE_MIN / numchunks, DSBSIZE_MAX / numchunks); |
| return (-1); |
| } |
| format.dwReserved = 0; |
| format.lpwfxFormat = wavefmt; |
| result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL); |
| if (result != DS_OK) { |
| SetDSerror("DirectSound CreateSoundBuffer", result); |
| return (-1); |
| } |
| IDirectSoundBuffer_SetFormat(*sndbuf, wavefmt); |
| |
| /* Silence the initial audio buffer */ |
| result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes, |
| (LPVOID *) & pvAudioPtr1, &dwAudioBytes1, |
| (LPVOID *) & pvAudioPtr2, &dwAudioBytes2, |
| DSBLOCK_ENTIREBUFFER); |
| if (result == DS_OK) { |
| SDL_memset(pvAudioPtr1, this->spec.silence, dwAudioBytes1); |
| IDirectSoundBuffer_Unlock(*sndbuf, |
| (LPVOID) pvAudioPtr1, dwAudioBytes1, |
| (LPVOID) pvAudioPtr2, dwAudioBytes2); |
| } |
| |
| /* We're ready to go */ |
| return (numchunks); |
| } |
| |
| static int |
| DSOUND_OpenDevice(_THIS, const char *devname, int iscapture) |
| { |
| HRESULT result; |
| WAVEFORMATEX waveformat; |
| int valid_format = 0; |
| SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); |
| |
| /* !!! FIXME: handle devname */ |
| /* !!! FIXME: handle iscapture */ |
| |
| /* Initialize all variables that we clean on shutdown */ |
| this->hidden = (struct SDL_PrivateAudioData *) |
| SDL_malloc((sizeof *this->hidden)); |
| if (this->hidden == NULL) { |
| SDL_OutOfMemory(); |
| return 0; |
| } |
| SDL_memset(this->hidden, 0, (sizeof *this->hidden)); |
| |
| while ((!valid_format) && (test_format)) { |
| switch (test_format) { |
| case AUDIO_U8: |
| case AUDIO_S16: |
| case AUDIO_S32: |
| this->spec.format = test_format; |
| valid_format = 1; |
| break; |
| } |
| test_format = SDL_NextAudioFormat(); |
| } |
| |
| if (!valid_format) { |
| DSOUND_CloseDevice(this); |
| SDL_SetError("DirectSound: Unsupported audio format"); |
| return 0; |
| } |
| |
| SDL_memset(&waveformat, 0, sizeof(waveformat)); |
| waveformat.wFormatTag = WAVE_FORMAT_PCM; |
| waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format); |
| waveformat.nChannels = this->spec.channels; |
| waveformat.nSamplesPerSec = this->spec.freq; |
| waveformat.nBlockAlign = |
| waveformat.nChannels * (waveformat.wBitsPerSample / 8); |
| waveformat.nAvgBytesPerSec = |
| waveformat.nSamplesPerSec * waveformat.nBlockAlign; |
| |
| /* Update the fragment size as size in bytes */ |
| SDL_CalculateAudioSpec(&this->spec); |
| |
| /* Open the audio device */ |
| result = DSoundCreate(NULL, &this->hidden->sound, NULL); |
| if (result != DS_OK) { |
| DSOUND_CloseDevice(this); |
| SetDSerror("DirectSoundCreate", result); |
| return 0; |
| } |
| |
| /* Create the audio buffer to which we write */ |
| this->hidden->num_buffers = CreateSecondary(this, mainwin, &waveformat); |
| if (this->hidden->num_buffers < 0) { |
| DSOUND_CloseDevice(this); |
| return 0; |
| } |
| |
| /* The buffer will auto-start playing in DSOUND_WaitDevice() */ |
| this->hidden->mixlen = this->spec.size; |
| |
| return 1; /* good to go. */ |
| } |
| |
| |
| static void |
| DSOUND_Deinitialize(void) |
| { |
| DSOUND_Unload(); |
| } |
| |
| |
| static int |
| DSOUND_Init(SDL_AudioDriverImpl *impl) |
| { |
| OSVERSIONINFO ver; |
| |
| /* |
| * Unfortunately, the sound drivers on NT have higher latencies than the |
| * audio buffers used by many SDL applications, so there are gaps in the |
| * audio - it sounds terrible. Punt for now. |
| */ |
| SDL_memset(&ver, '\0', sizeof (OSVERSIONINFO)); |
| ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); |
| GetVersionEx(&ver); |
| if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT) |
| if (ver.dwMajorVersion <= 4) { |
| return 0; /* NT4.0 or earlier. Disable dsound support. */ |
| } |
| } |
| |
| if (!DSOUND_Load()) { |
| return 0; |
| } |
| |
| /* Set the function pointers */ |
| impl->OpenDevice = DSOUND_OpenDevice; |
| impl->PlayDevice = DSOUND_PlayDevice; |
| impl->WaitDevice = DSOUND_WaitDevice; |
| impl->WaitDone = DSOUND_WaitDone; |
| impl->ThreadInit = DSOUND_ThreadInit; |
| impl->GetDeviceBuf = DSOUND_GetDeviceBuf; |
| impl->CloseDevice = DSOUND_CloseDevice; |
| impl->Deinitialize = DSOUND_Deinitialize; |
| impl->OnlyHasDefaultOutputDevice = 1; /* !!! FIXME */ |
| |
| return 1; |
| } |
| |
| AudioBootStrap DSOUND_bootstrap = { |
| "dsound", WINDOWS_OS_NAME "DirectSound", DSOUND_Init, 0 |
| }; |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |