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