src/audio/xaudio2/SDL_xaudio2.c
author Ryan C. Gordon
Mon, 22 Aug 2011 14:37:45 -0400
changeset 5635 bef0f093c9da
parent 5634 093e60544778
child 5636 44bfdab5eaf1
permissions -rw-r--r--
Removed SDL_xaudio2.h ... no real need for this to be separate.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2011 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "SDL_config.h"
    22 #include "../../core/windows/SDL_windows.h"
    23 #include "SDL_audio.h"
    24 #include "../SDL_audio_c.h"
    25 #include "SDL_assert.h"
    26 
    27 #include "../SDL_sysaudio.h"
    28 
    29 #if SDL_AUDIO_DRIVER_XAUDIO2
    30 #include <dxsdkver.h> /* XAudio2 exists as of the March 2008 DirectX SDK */
    31 #if (defined(_DXSDK_BUILD_MAJOR) && (_DXSDK_BUILD_MAJOR >= 1284))
    32 #   define SDL_HAVE_XAUDIO2_H 1
    33 #endif
    34 #endif
    35 
    36 #ifdef SDL_HAVE_XAUDIO2_H
    37 
    38 #define INITGUID 1
    39 #include <XAudio2.h>
    40 
    41 /* Hidden "this" pointer for the audio functions */
    42 #define _THIS	SDL_AudioDevice *this
    43 
    44 struct SDL_PrivateAudioData
    45 {
    46     IXAudio2 *ixa2;
    47     IXAudio2SourceVoice *source;
    48     IXAudio2MasteringVoice *mastering;
    49     HANDLE semaphore;
    50     Uint8 *mixbuf;
    51     int mixlen;
    52     Uint8 *nextbuf;
    53 };
    54 
    55 
    56 static __inline__ char *
    57 utf16_to_utf8(const WCHAR *S)
    58 {
    59     /* !!! FIXME: this should be UTF-16, not UCS-2! */
    60     return SDL_iconv_string("UTF-8", "UCS-2", (char *)(S),
    61                             (SDL_wcslen(S)+1)*sizeof(WCHAR));
    62 }
    63 
    64 static void
    65 XAUDIO2_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
    66 {
    67     IXAudio2 *ixa2 = NULL;
    68     UINT32 devcount = 0;
    69     UINT32 i = 0;
    70     void *ptr = NULL;
    71 
    72     if (iscapture) {
    73         SDL_SetError("XAudio2: capture devices unsupported.");
    74         return;
    75     } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
    76         SDL_SetError("XAudio2: XAudio2Create() failed.");
    77         return;
    78     } else if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
    79         SDL_SetError("XAudio2: IXAudio2::GetDeviceCount() failed.");
    80         IXAudio2_Release(ixa2);
    81         return;
    82     }
    83 
    84     for (i = 0; i < devcount; i++) {
    85         XAUDIO2_DEVICE_DETAILS details;
    86         if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) {
    87             char *str = utf16_to_utf8(details.DisplayName);
    88             if (str != NULL) {
    89                 addfn(str);
    90                 SDL_free(str);  /* addfn() made a copy of the string. */
    91             }
    92         }
    93     }
    94 
    95     IXAudio2_Release(ixa2);
    96 }
    97 
    98 static void STDMETHODCALLTYPE
    99 VoiceCBOnBufferEnd(THIS_ void *data)
   100 {
   101     /* Just signal the SDL audio thread and get out of XAudio2's way. */
   102     SDL_AudioDevice *this = (SDL_AudioDevice *) data;
   103     ReleaseSemaphore(this->hidden->semaphore, 1, NULL);
   104 }
   105 
   106 static void STDMETHODCALLTYPE
   107 VoiceCBOnVoiceError(THIS_ void *data, HRESULT Error)
   108 {
   109     /* !!! FIXME: attempt to recover, or mark device disconnected. */
   110     SDL_assert(0 && "write me!");
   111 }
   112 
   113 /* no-op callbacks... */
   114 static void STDMETHODCALLTYPE VoiceCBOnStreamEnd(THIS) {}
   115 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b) {}
   116 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassEnd(THIS) {}
   117 static void STDMETHODCALLTYPE VoiceCBOnBufferStart(THIS_ void *data) {}
   118 static void STDMETHODCALLTYPE VoiceCBOnLoopEnd(THIS_ void *data) {}
   119 
   120 
   121 static Uint8 *
   122 XAUDIO2_GetDeviceBuf(_THIS)
   123 {
   124     return this->hidden->nextbuf;
   125 }
   126 
   127 static void
   128 XAUDIO2_PlayDevice(_THIS)
   129 {
   130     XAUDIO2_BUFFER buffer;
   131     Uint8 *mixbuf = this->hidden->mixbuf;
   132     Uint8 *nextbuf = this->hidden->nextbuf;
   133     const int mixlen = this->hidden->mixlen;
   134     IXAudio2SourceVoice *source = this->hidden->source;
   135     HRESULT result = S_OK;
   136 
   137     if (!this->enabled) { /* shutting down? */
   138         return;
   139     }
   140 
   141     /* Submit the next filled buffer */
   142     SDL_zero(buffer);
   143     buffer.AudioBytes = mixlen;
   144     buffer.pAudioData = nextbuf;
   145     buffer.pContext = this;
   146 
   147     if (nextbuf == mixbuf) {
   148         nextbuf += mixlen;
   149     } else {
   150         nextbuf = mixbuf;
   151     }
   152     this->hidden->nextbuf = nextbuf;
   153 
   154     result = IXAudio2SourceVoice_SubmitSourceBuffer(source, &buffer, NULL);
   155     if (result == XAUDIO2_E_DEVICE_INVALIDATED) {
   156         /* !!! FIXME: possibly disconnected or temporary lost. Recover? */
   157     }
   158 
   159     if (result != S_OK) {  /* uhoh, panic! */
   160         IXAudio2SourceVoice_FlushSourceBuffers(source);
   161         this->enabled = 0;
   162     }
   163 }
   164 
   165 static void
   166 XAUDIO2_WaitDevice(_THIS)
   167 {
   168     if (this->enabled) {
   169         WaitForSingleObject(this->hidden->semaphore, INFINITE);
   170     }
   171 }
   172 
   173 static void
   174 XAUDIO2_WaitDone(_THIS)
   175 {
   176     IXAudio2SourceVoice *source = this->hidden->source;
   177     XAUDIO2_VOICE_STATE state;
   178     SDL_assert(!this->enabled);  /* flag that stops playing. */
   179     IXAudio2SourceVoice_Discontinuity(source);
   180     IXAudio2SourceVoice_GetState(source, &state);
   181     while (state.BuffersQueued > 0) {
   182         WaitForSingleObject(this->hidden->semaphore, INFINITE);
   183         IXAudio2SourceVoice_GetState(source, &state);
   184     }
   185 }
   186 
   187 
   188 static void
   189 XAUDIO2_CloseDevice(_THIS)
   190 {
   191     if (this->hidden != NULL) {
   192         IXAudio2 *ixa2 = this->hidden->ixa2;
   193         IXAudio2SourceVoice *source = this->hidden->source;
   194         IXAudio2MasteringVoice *mastering = this->hidden->mastering;
   195 
   196         if (source != NULL) {
   197             IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW);
   198             IXAudio2SourceVoice_FlushSourceBuffers(source);
   199             IXAudio2SourceVoice_DestroyVoice(source);
   200         }
   201         if (ixa2 != NULL) {
   202             IXAudio2_StopEngine(ixa2);
   203         }
   204         if (mastering != NULL) {
   205             IXAudio2MasteringVoice_DestroyVoice(mastering);
   206         }
   207         if (ixa2 != NULL) {
   208             IXAudio2_Release(ixa2);
   209         }
   210         if (this->hidden->mixbuf != NULL) {
   211             SDL_free(this->hidden->mixbuf);
   212         }
   213         if (this->hidden->semaphore != NULL) {
   214             CloseHandle(this->hidden->semaphore);
   215         }
   216 
   217         SDL_free(this->hidden);
   218         this->hidden = NULL;
   219     }
   220 }
   221 
   222 static int
   223 XAUDIO2_OpenDevice(_THIS, const char *devname, int iscapture)
   224 {
   225     HRESULT result = S_OK;
   226     WAVEFORMATEX waveformat;
   227     int valid_format = 0;
   228     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   229     IXAudio2 *ixa2 = NULL;
   230     IXAudio2SourceVoice *source = NULL;
   231     UINT32 devId = 0;  /* 0 == system default device. */
   232 
   233 	static IXAudio2VoiceCallbackVtbl callbacks_vtable = {
   234 	    VoiceCBOnVoiceProcessPassStart,
   235         VoiceCBOnVoiceProcessPassEnd,
   236         VoiceCBOnStreamEnd,
   237         VoiceCBOnBufferStart,
   238         VoiceCBOnBufferEnd,
   239         VoiceCBOnLoopEnd,
   240         VoiceCBOnVoiceError
   241 	};
   242 
   243 	static IXAudio2VoiceCallback callbacks = { &callbacks_vtable };
   244 
   245     if (iscapture) {
   246         SDL_SetError("XAudio2: capture devices unsupported.");
   247         return 0;
   248     } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
   249         SDL_SetError("XAudio2: XAudio2Create() failed.");
   250         return 0;
   251     }
   252 
   253     if (devname != NULL) {
   254         UINT32 devcount = 0;
   255         UINT32 i = 0;
   256 
   257         if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
   258             IXAudio2_Release(ixa2);
   259             SDL_SetError("XAudio2: IXAudio2_GetDeviceCount() failed.");
   260             return 0;
   261         }
   262         for (i = 0; i < devcount; i++) {
   263             XAUDIO2_DEVICE_DETAILS details;
   264             if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) {
   265                 char *str = utf16_to_utf8(details.DisplayName);
   266                 if (str != NULL) {
   267                     const int match = (SDL_strcmp(str, devname) == 0);
   268                     SDL_free(str);
   269                     if (match) {
   270                         devId = i;
   271                         break;
   272                     }
   273                 }
   274             }
   275         }
   276 
   277         if (i == devcount) {
   278             IXAudio2_Release(ixa2);
   279             SDL_SetError("XAudio2: Requested device not found.");
   280             return 0;
   281         }
   282     }
   283 
   284     /* Initialize all variables that we clean on shutdown */
   285     this->hidden = (struct SDL_PrivateAudioData *)
   286         SDL_malloc((sizeof *this->hidden));
   287     if (this->hidden == NULL) {
   288         IXAudio2_Release(ixa2);
   289         SDL_OutOfMemory();
   290         return 0;
   291     }
   292     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   293 
   294     this->hidden->ixa2 = ixa2;
   295     this->hidden->semaphore = CreateSemaphore(NULL, 1, 2, NULL);
   296     if (this->hidden->semaphore == NULL) {
   297         XAUDIO2_CloseDevice(this);
   298         SDL_SetError("XAudio2: CreateSemaphore() failed!");
   299         return 0;
   300     }
   301 
   302     while ((!valid_format) && (test_format)) {
   303         switch (test_format) {
   304         case AUDIO_U8:
   305         case AUDIO_S16:
   306         case AUDIO_S32:
   307         case AUDIO_F32:
   308             this->spec.format = test_format;
   309             valid_format = 1;
   310             break;
   311         }
   312         test_format = SDL_NextAudioFormat();
   313     }
   314 
   315     if (!valid_format) {
   316         XAUDIO2_CloseDevice(this);
   317         SDL_SetError("XAudio2: Unsupported audio format");
   318         return 0;
   319     }
   320 
   321     /* Update the fragment size as size in bytes */
   322     SDL_CalculateAudioSpec(&this->spec);
   323 
   324     /* We feed a Source, it feeds the Mastering, which feeds the device. */
   325     this->hidden->mixlen = this->spec.size;
   326     this->hidden->mixbuf = (Uint8 *) SDL_malloc(2 * this->hidden->mixlen);
   327     if (this->hidden->mixbuf == NULL) {
   328         XAUDIO2_CloseDevice(this);
   329         SDL_OutOfMemory();
   330         return 0;
   331     }
   332     this->hidden->nextbuf = this->hidden->mixbuf;
   333     SDL_memset(this->hidden->mixbuf, '\0', 2 * this->hidden->mixlen);
   334 
   335     /* We use XAUDIO2_DEFAULT_CHANNELS instead of this->spec.channels. On
   336        Xbox360, this means 5.1 output, but on Windows, it means "figure out
   337        what the system has." It might be preferable to let XAudio2 blast
   338        stereo output to appropriate surround sound configurations
   339        instead of clamping to 2 channels, even though we'll configure the
   340        Source Voice for whatever number of channels you supply. */
   341     result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
   342                                            XAUDIO2_DEFAULT_CHANNELS,
   343                                            this->spec.freq, 0, devId, NULL);
   344     if (result != S_OK) {
   345         XAUDIO2_CloseDevice(this);
   346         SDL_SetError("XAudio2: Couldn't create mastering voice");
   347         return 0;
   348     }
   349 
   350     SDL_zero(waveformat);
   351     if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
   352         waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
   353     } else {
   354         waveformat.wFormatTag = WAVE_FORMAT_PCM;
   355     }
   356     waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
   357     waveformat.nChannels = this->spec.channels;
   358     waveformat.nSamplesPerSec = this->spec.freq;
   359     waveformat.nBlockAlign =
   360         waveformat.nChannels * (waveformat.wBitsPerSample / 8);
   361     waveformat.nAvgBytesPerSec =
   362         waveformat.nSamplesPerSec * waveformat.nBlockAlign;
   363 
   364     result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
   365                                         XAUDIO2_VOICE_NOSRC |
   366                                         XAUDIO2_VOICE_NOPITCH,
   367                                         1.0f, &callbacks, NULL, NULL);
   368     if (result != S_OK) {
   369         XAUDIO2_CloseDevice(this);
   370         SDL_SetError("XAudio2: Couldn't create source voice");
   371         return 0;
   372     }
   373     this->hidden->source = source;
   374 
   375     /* Start everything playing! */
   376     result = IXAudio2_StartEngine(ixa2);
   377     if (result != S_OK) {
   378         XAUDIO2_CloseDevice(this);
   379         SDL_SetError("XAudio2: Couldn't start engine");
   380         return 0;
   381     }
   382 
   383     result = IXAudio2SourceVoice_Start(source, 0, XAUDIO2_COMMIT_NOW);
   384     if (result != S_OK) {
   385         XAUDIO2_CloseDevice(this);
   386         SDL_SetError("XAudio2: Couldn't start source voice");
   387         return 0;
   388     }
   389 
   390     return 1; /* good to go. */
   391 }
   392 
   393 static void
   394 XAUDIO2_Deinitialize(void)
   395 {
   396     WIN_CoUninitialize();
   397 }
   398 
   399 #endif  /* SDL_HAVE_XAUDIO2_H */
   400 
   401 
   402 static int
   403 XAUDIO2_Init(SDL_AudioDriverImpl * impl)
   404 {
   405 #if !SDL_HAVE_XAUDIO2_H
   406     return 0;  /* no XAudio2 support, ever. Update your SDK! */
   407 #else
   408     /* XAudio2Create() is a macro that uses COM; we don't load the .dll */
   409     IXAudio2 *ixa2 = NULL;
   410     if (FAILED(WIN_CoInitialize())) {
   411         SDL_SetError("XAudio2: CoInitialize() failed");
   412         return 0;
   413     }
   414 
   415     if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
   416         WIN_CoUninitialize();
   417         SDL_SetError("XAudio2: XAudio2Create() failed");
   418         return 0;  /* not available. */
   419     }
   420     IXAudio2_Release(ixa2);
   421 
   422     /* Set the function pointers */
   423     impl->DetectDevices = XAUDIO2_DetectDevices;
   424     impl->OpenDevice = XAUDIO2_OpenDevice;
   425     impl->PlayDevice = XAUDIO2_PlayDevice;
   426     impl->WaitDevice = XAUDIO2_WaitDevice;
   427     impl->WaitDone = XAUDIO2_WaitDone;
   428     impl->GetDeviceBuf = XAUDIO2_GetDeviceBuf;
   429     impl->CloseDevice = XAUDIO2_CloseDevice;
   430     impl->Deinitialize = XAUDIO2_Deinitialize;
   431 
   432     return 1;   /* this audio target is available. */
   433 #endif
   434 }
   435 
   436 AudioBootStrap XAUDIO2_bootstrap = {
   437     "xaudio2", "XAudio2", XAUDIO2_Init, 0
   438 };
   439 
   440 /* vi: set ts=4 sw=4 expandtab: */