src/audio/xaudio2/SDL_xaudio2.c
author Ryan C. Gordon <icculus@icculus.org>
Thu, 04 Aug 2011 01:07:09 -0400
changeset 5592 2e88d0742f4d
child 5593 ab22ca13c47f
permissions -rw-r--r--
Implemented XAudio2 target for Windows (and Xbox360, theoretically!).
     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 #define INITGUID 1
    28 #include "SDL_xaudio2.h"
    29 
    30 /* !!! FIXME: this is a cut and paste of SDL_FreeUnixAudioDevices(),
    31  * !!! FIXME:  which is more proof this needs to be managed in SDL_audio.c
    32  * !!! FIXME:  and not in drivers.
    33  */
    34 static void
    35 FreeXAudio2AudioDevices(char ***devices, int *devCount)
    36 {
    37     int i = *devCount;
    38     if ((i > 0) && (*devices != NULL)) {
    39         while (i--) {
    40             SDL_free((*devices)[i]);
    41         }
    42     }
    43 
    44     if (*devices != NULL) {
    45         SDL_free(*devices);
    46     }
    47 
    48     *devices = NULL;
    49     *devCount = 0;
    50 }
    51 
    52 
    53 static char **outputDevices = NULL;
    54 static int outputDeviceCount = 0;
    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 int
    65 XAUDIO2_DetectDevices(int iscapture)
    66 {
    67     IXAudio2 *ixa2 = NULL;
    68     UINT32 devcount = 0;
    69     UINT32 i = 0;
    70     void *ptr = NULL;
    71 
    72     if (!iscapture) {
    73         FreeXAudio2AudioDevices(&outputDevices, &outputDeviceCount);
    74     }
    75 
    76     if (iscapture) {
    77         SDL_SetError("XAudio2: capture devices unsupported.");
    78         return 0;
    79     } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
    80         SDL_SetError("XAudio2: XAudio2Create() failed.");
    81         return 0;
    82     } else if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
    83         SDL_SetError("XAudio2: IXAudio2::GetDeviceCount() failed.");
    84         IXAudio2_Release(ixa2);
    85         return 0;
    86     } else if ((ptr = SDL_malloc(sizeof (char *) * devcount)) == NULL) {
    87         SDL_OutOfMemory();
    88         IXAudio2_Release(ixa2);
    89         return 0;
    90     }
    91 
    92     outputDevices = (char **) ptr;
    93     for (i = 0; i < devcount; i++) {
    94         XAUDIO2_DEVICE_DETAILS details;
    95         if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) {
    96             char *str = utf16_to_utf8(details.DisplayName);
    97             if (str != NULL) {
    98                 outputDevices[outputDeviceCount++] = str;
    99             }
   100         }
   101     }
   102 
   103     IXAudio2_Release(ixa2);
   104 
   105     return outputDeviceCount;
   106 }
   107 
   108 static const char *
   109 XAUDIO2_GetDeviceName(int index, int iscapture)
   110 {
   111     if ((!iscapture) && (index < outputDeviceCount)) {
   112         return outputDevices[index];
   113     }
   114 
   115     SDL_SetError("XAudio2: No such device");
   116     return NULL;
   117 }
   118 
   119 static void STDMETHODCALLTYPE
   120 VoiceCBOnBufferEnd(THIS_ void *data)
   121 {
   122     /* Just signal the SDL audio thread and get out of XAudio2's way. */
   123     SDL_AudioDevice *this = (SDL_AudioDevice *) data;
   124     ReleaseSemaphore(this->hidden->semaphore, 1, NULL);
   125 }
   126 
   127 static void STDMETHODCALLTYPE
   128 VoiceCBOnVoiceError(THIS_ void *data, HRESULT Error)
   129 {
   130     /* !!! FIXME: attempt to recover, or mark device disconnected. */
   131     SDL_assert(0 && "write me!");
   132 }
   133 
   134 /* no-op callbacks... */
   135 static void STDMETHODCALLTYPE VoiceCBOnStreamEnd(THIS) {}
   136 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b) {}
   137 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassEnd(THIS) {}
   138 static void STDMETHODCALLTYPE VoiceCBOnBufferStart(THIS_ void *data) {}
   139 static void STDMETHODCALLTYPE VoiceCBOnLoopEnd(THIS_ void *data) {}
   140 
   141 
   142 static Uint8 *
   143 XAUDIO2_GetDeviceBuf(_THIS)
   144 {
   145     return this->hidden->nextbuf;
   146 }
   147 
   148 static void
   149 XAUDIO2_PlayDevice(_THIS)
   150 {
   151     XAUDIO2_BUFFER buffer;
   152     Uint8 *mixbuf = this->hidden->mixbuf;
   153     Uint8 *nextbuf = this->hidden->nextbuf;
   154     const int mixlen = this->hidden->mixlen;
   155     IXAudio2SourceVoice *source = this->hidden->source;
   156     HRESULT result = S_OK;
   157 
   158     if (!this->enabled) { /* shutting down? */
   159         return;
   160     }
   161 
   162     /* Submit the next filled buffer */
   163     SDL_zero(buffer);
   164     buffer.AudioBytes = mixlen;
   165     buffer.pAudioData = nextbuf;
   166     buffer.pContext = this;
   167 
   168     if (nextbuf == mixbuf) {
   169         nextbuf += mixlen;
   170     } else {
   171         nextbuf = mixbuf;
   172     }
   173     this->hidden->nextbuf = nextbuf;
   174 
   175     result = IXAudio2SourceVoice_SubmitSourceBuffer(source, &buffer, NULL);
   176     if (result == XAUDIO2_E_DEVICE_INVALIDATED) {
   177         /* !!! FIXME: possibly disconnected or temporary lost. Recover? */
   178     }
   179 
   180     if (result != S_OK) {  /* uhoh, panic! */
   181         IXAudio2SourceVoice_FlushSourceBuffers(source);
   182         this->enabled = 0;
   183     }
   184 }
   185 
   186 static void
   187 XAUDIO2_WaitDevice(_THIS)
   188 {
   189     if (this->enabled) {
   190         WaitForSingleObject(this->hidden->semaphore, INFINITE);
   191     }
   192 }
   193 
   194 static void
   195 XAUDIO2_WaitDone(_THIS)
   196 {
   197     IXAudio2SourceVoice *source = this->hidden->source;
   198     XAUDIO2_VOICE_STATE state;
   199     SDL_assert(!this->enabled);  /* flag that stops playing. */
   200     IXAudio2SourceVoice_Discontinuity(source);
   201     IXAudio2SourceVoice_GetState(source, &state);
   202     while (state.BuffersQueued > 0) {
   203         WaitForSingleObject(this->hidden->semaphore, INFINITE);
   204         IXAudio2SourceVoice_GetState(source, &state);
   205     }
   206 }
   207 
   208 
   209 static void
   210 XAUDIO2_CloseDevice(_THIS)
   211 {
   212     if (this->hidden != NULL) {
   213         IXAudio2 *ixa2 = this->hidden->ixa2;
   214         IXAudio2SourceVoice *source = this->hidden->source;
   215         IXAudio2MasteringVoice *mastering = this->hidden->mastering;
   216 
   217         if (source != NULL) {
   218             IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW);
   219             IXAudio2SourceVoice_FlushSourceBuffers(source);
   220             IXAudio2SourceVoice_DestroyVoice(source);
   221         }
   222         if (ixa2 != NULL) {
   223             IXAudio2_StopEngine(ixa2);
   224         }
   225         if (mastering != NULL) {
   226             IXAudio2MasteringVoice_DestroyVoice(mastering);
   227         }
   228         if (ixa2 != NULL) {
   229             IXAudio2_Release(ixa2);
   230         }
   231         if (this->hidden->mixbuf != NULL) {
   232             SDL_free(this->hidden->mixbuf);
   233         }
   234         if (this->hidden->semaphore != NULL) {
   235             CloseHandle(this->hidden->semaphore);
   236         }
   237 
   238         SDL_free(this->hidden);
   239         this->hidden = NULL;
   240     }
   241 }
   242 
   243 static int
   244 XAUDIO2_OpenDevice(_THIS, const char *devname, int iscapture)
   245 {
   246     HRESULT result = S_OK;
   247     WAVEFORMATEX waveformat;
   248     int valid_format = 0;
   249     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   250     IXAudio2 *ixa2 = NULL;
   251     IXAudio2SourceVoice *source = NULL;
   252     UINT32 devId = 0;  /* 0 == system default device. */
   253 
   254 	static IXAudio2VoiceCallbackVtbl callbacks_vtable = {
   255 	    VoiceCBOnVoiceProcessPassStart,
   256         VoiceCBOnVoiceProcessPassEnd,
   257         VoiceCBOnStreamEnd,
   258         VoiceCBOnBufferStart,
   259         VoiceCBOnBufferEnd,
   260         VoiceCBOnLoopEnd,
   261         VoiceCBOnVoiceError
   262 	};
   263 
   264 	static IXAudio2VoiceCallback callbacks = { &callbacks_vtable };
   265 
   266     if (iscapture) {
   267         SDL_SetError("XAudio2: capture devices unsupported.");
   268         return 0;
   269     } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
   270         SDL_SetError("XAudio2: XAudio2Create() failed.");
   271         return 0;
   272     }
   273 
   274     if (devname != NULL) {
   275         UINT32 devcount = 0;
   276         UINT32 i = 0;
   277 
   278         if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
   279             IXAudio2_Release(ixa2);
   280             SDL_SetError("XAudio2: IXAudio2_GetDeviceCount() failed.");
   281             return 0;
   282         }
   283         for (i = 0; i < devcount; i++) {
   284             XAUDIO2_DEVICE_DETAILS details;
   285             if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) {
   286                 char *str = utf16_to_utf8(details.DisplayName);
   287                 if (str != NULL) {
   288                     const int match = (SDL_strcmp(str, devname) == 0);
   289                     SDL_free(str);
   290                     if (match) {
   291                         devId = i;
   292                         break;
   293                     }
   294                 }
   295             }
   296         }
   297 
   298         if (i == devcount) {
   299             IXAudio2_Release(ixa2);
   300             SDL_SetError("XAudio2: Requested device not found.");
   301             return 0;
   302         }
   303     }
   304 
   305     /* Initialize all variables that we clean on shutdown */
   306     this->hidden = (struct SDL_PrivateAudioData *)
   307         SDL_malloc((sizeof *this->hidden));
   308     if (this->hidden == NULL) {
   309         IXAudio2_Release(ixa2);
   310         SDL_OutOfMemory();
   311         return 0;
   312     }
   313     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   314 
   315     this->hidden->ixa2 = ixa2;
   316     this->hidden->semaphore = CreateSemaphore(NULL, 1, 2, NULL);
   317     if (this->hidden->semaphore == NULL) {
   318         XAUDIO2_CloseDevice(this);
   319         SDL_SetError("XAudio2: CreateSemaphore() failed!");
   320         return 0;
   321     }
   322 
   323     while ((!valid_format) && (test_format)) {
   324         switch (test_format) {
   325         case AUDIO_U8:
   326         case AUDIO_S16:
   327         case AUDIO_S32:
   328         case AUDIO_F32:
   329             this->spec.format = test_format;
   330             valid_format = 1;
   331             break;
   332         }
   333         test_format = SDL_NextAudioFormat();
   334     }
   335 
   336     if (!valid_format) {
   337         XAUDIO2_CloseDevice(this);
   338         SDL_SetError("XAudio2: Unsupported audio format");
   339         return 0;
   340     }
   341 
   342     /* Update the fragment size as size in bytes */
   343     SDL_CalculateAudioSpec(&this->spec);
   344 
   345     /* We feed a Source, it feeds the Mastering, which feeds the device. */
   346     this->hidden->mixlen = this->spec.size;
   347     this->hidden->mixbuf = (Uint8 *) SDL_malloc(2 * this->hidden->mixlen);
   348     if (this->hidden->mixbuf == NULL) {
   349         XAUDIO2_CloseDevice(this);
   350         SDL_OutOfMemory();
   351         return 0;
   352     }
   353     this->hidden->nextbuf = this->hidden->mixbuf;
   354     SDL_memset(this->hidden->mixbuf, '\0', 2 * this->hidden->mixlen);
   355 
   356     /* We use XAUDIO2_DEFAULT_CHANNELS instead of this->spec.channels. On
   357        Xbox360, this means 5.1 output, but on Windows, it means "figure out
   358        what the system has." It might be preferable to let XAudio2 blast
   359        stereo output to appropriate surround sound configurations
   360        instead of clamping to 2 channels, even though we'll configure the
   361        Source Voice for whatever number of channels you supply. */
   362     result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
   363                                            XAUDIO2_DEFAULT_CHANNELS,
   364                                            this->spec.freq, 0, devId, NULL);
   365     if (result != S_OK) {
   366         XAUDIO2_CloseDevice(this);
   367         SDL_SetError("XAudio2: Couldn't create mastering voice");
   368         return 0;
   369     }
   370 
   371     SDL_zero(waveformat);
   372     if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
   373         waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
   374     } else {
   375         waveformat.wFormatTag = WAVE_FORMAT_PCM;
   376     }
   377     waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
   378     waveformat.nChannels = this->spec.channels;
   379     waveformat.nSamplesPerSec = this->spec.freq;
   380     waveformat.nBlockAlign =
   381         waveformat.nChannels * (waveformat.wBitsPerSample / 8);
   382     waveformat.nAvgBytesPerSec =
   383         waveformat.nSamplesPerSec * waveformat.nBlockAlign;
   384 
   385     result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
   386                                         XAUDIO2_VOICE_NOSRC |
   387                                         XAUDIO2_VOICE_NOPITCH,
   388                                         1.0f, &callbacks, NULL, NULL);
   389     if (result != S_OK) {
   390         XAUDIO2_CloseDevice(this);
   391         SDL_SetError("XAudio2: Couldn't create source voice");
   392         return 0;
   393     }
   394     this->hidden->source = source;
   395 
   396     /* Start everything playing! */
   397     result = IXAudio2_StartEngine(ixa2);
   398     if (result != S_OK) {
   399         XAUDIO2_CloseDevice(this);
   400         SDL_SetError("XAudio2: Couldn't start engine");
   401         return 0;
   402     }
   403 
   404     result = IXAudio2SourceVoice_Start(source, 0, XAUDIO2_COMMIT_NOW);
   405     if (result != S_OK) {
   406         XAUDIO2_CloseDevice(this);
   407         SDL_SetError("XAudio2: Couldn't start source voice");
   408         return 0;
   409     }
   410 
   411     return 1; /* good to go. */
   412 }
   413 
   414 static void
   415 XAUDIO2_Deinitialize(void)
   416 {
   417     WIN_CoUninitialize();
   418 }
   419 
   420 static int
   421 XAUDIO2_Init(SDL_AudioDriverImpl * impl)
   422 {
   423     /* XAudio2Create() is a macro that uses COM; we don't load the .dll */
   424     IXAudio2 *ixa2 = NULL;
   425     if (FAILED(WIN_CoInitialize())) {
   426         SDL_SetError("XAudio2: CoInitialize() failed");
   427         return 0;
   428     }
   429 
   430     if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
   431         WIN_CoUninitialize();
   432         SDL_SetError("XAudio2: XAudio2Create() failed");
   433         return 0;  /* not available. */
   434     }
   435     IXAudio2_Release(ixa2);
   436 
   437     /* Set the function pointers */
   438     impl->DetectDevices = XAUDIO2_DetectDevices;
   439     impl->GetDeviceName = XAUDIO2_GetDeviceName;
   440     impl->OpenDevice = XAUDIO2_OpenDevice;
   441     impl->PlayDevice = XAUDIO2_PlayDevice;
   442     impl->WaitDevice = XAUDIO2_WaitDevice;
   443     impl->WaitDone = XAUDIO2_WaitDone;
   444     impl->GetDeviceBuf = XAUDIO2_GetDeviceBuf;
   445     impl->CloseDevice = XAUDIO2_CloseDevice;
   446     impl->Deinitialize = XAUDIO2_Deinitialize;
   447 
   448     return 1;   /* this audio target is available. */
   449 }
   450 
   451 AudioBootStrap XAUDIO2_bootstrap = {
   452     "xaudio2", "XAudio2", XAUDIO2_Init, 0
   453 };
   454 
   455 /* vi: set ts=4 sw=4 expandtab: */