src/audio/xaudio2/SDL_xaudio2.c
author Ryan C. Gordon
Tue, 24 Jan 2017 16:18:25 -0500
changeset 10850 c9dc0068b0e7
parent 10737 3406a0f8b041
child 10877 3366ca004d17
permissions -rw-r--r--
configure: report libsamplerate support status.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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 
    22 /* WinRT NOTICE:
    23 
    24    A few changes to SDL's XAudio2 backend were warranted by API
    25    changes to Windows.  Many, but not all of these are documented by Microsoft
    26    at:
    27    http://blogs.msdn.com/b/chuckw/archive/2012/04/02/xaudio2-and-windows-8-consumer-preview.aspx
    28 
    29    1. Windows' thread synchronization function, CreateSemaphore, was removed
    30       from WinRT.  SDL's semaphore API was substituted instead.
    31    2. The method calls, IXAudio2::GetDeviceCount and IXAudio2::GetDeviceDetails
    32       were removed from the XAudio2 API.  Microsoft is telling developers to
    33       use APIs in Windows::Foundation instead.
    34       For SDL, the missing methods were reimplemented using the APIs Microsoft
    35       said to use.
    36    3. CoInitialize and CoUninitialize are not available in WinRT.
    37       These calls were removed, as COM will have been initialized earlier,
    38       at least by the call to the WinRT app's main function
    39       (aka 'int main(Platform::Array<Platform::String^>^)).  (DLudwig:
    40       This was my understanding of how WinRT: the 'main' function uses
    41       a tag of [MTAThread], which should initialize COM.  My understanding
    42       of COM is somewhat limited, and I may be incorrect here.)
    43    4. IXAudio2::CreateMasteringVoice changed its integer-based 'DeviceIndex'
    44       argument to a string-based one, 'szDeviceId'.  In WinRT, the
    45       string-based argument will be used.
    46 */
    47 #include "../../SDL_internal.h"
    48 
    49 #if SDL_AUDIO_DRIVER_XAUDIO2
    50 
    51 #include "../../core/windows/SDL_windows.h"
    52 #include "SDL_audio.h"
    53 #include "../SDL_audio_c.h"
    54 #include "../SDL_sysaudio.h"
    55 #include "SDL_assert.h"
    56 
    57 #ifdef __GNUC__
    58 /* The configure script already did any necessary checking */
    59 #  define SDL_XAUDIO2_HAS_SDK 1
    60 #elif defined(__WINRT__)
    61 /* WinRT always has access to the XAudio 2 SDK (albeit with a header file
    62    that doesn't compile as C code).
    63 */
    64 #  define SDL_XAUDIO2_HAS_SDK
    65 #include "SDL_xaudio2.h"    /* ... compiles as C code, in contrast to XAudio2 headers
    66                                in the Windows SDK, v.10.0.10240.0 (Win 10's initial SDK)
    67                              */
    68 #else
    69 /* XAudio2 exists in the last DirectX SDK as well as the latest Windows SDK.
    70    To enable XAudio2 support, you will need to add the location of your DirectX SDK headers to
    71    the SDL projects additional include directories and then set SDL_XAUDIO2_HAS_SDK=1 as a
    72    preprocessor define
    73  */
    74 #if 0 /* See comment above */
    75 #include <dxsdkver.h>
    76 #if (!defined(_DXSDK_BUILD_MAJOR) || (_DXSDK_BUILD_MAJOR < 1284))
    77 #  pragma message("Your DirectX SDK is too old. Disabling XAudio2 support.")
    78 #else
    79 #  define SDL_XAUDIO2_HAS_SDK 1
    80 #endif
    81 #endif /* 0 */
    82 #endif /* __GNUC__ */
    83 
    84 #ifdef SDL_XAUDIO2_HAS_SDK
    85 
    86 /* Check to see if we're compiling for XAudio 2.8, or higher. */
    87 #ifdef WINVER
    88 #if WINVER >= 0x0602  /* Windows 8 SDK or higher? */
    89 #define SDL_XAUDIO2_WIN8 1
    90 #endif
    91 #endif
    92 
    93 #if !defined(_SDL_XAUDIO2_H)
    94 #define INITGUID 1
    95 #include <xaudio2.h>
    96 #endif
    97 
    98 /* Hidden "this" pointer for the audio functions */
    99 #define _THIS   SDL_AudioDevice *this
   100 
   101 #ifdef __WINRT__
   102 #include "SDL_xaudio2_winrthelpers.h"
   103 #endif
   104 
   105 /* Fixes bug 1210 where some versions of gcc need named parameters */
   106 #ifdef __GNUC__
   107 #ifdef THIS
   108 #undef THIS
   109 #endif
   110 #define THIS    INTERFACE *p
   111 #ifdef THIS_
   112 #undef THIS_
   113 #endif
   114 #define THIS_   INTERFACE *p,
   115 #endif
   116 
   117 struct SDL_PrivateAudioData
   118 {
   119     IXAudio2 *ixa2;
   120     IXAudio2SourceVoice *source;
   121     IXAudio2MasteringVoice *mastering;
   122     SDL_sem * semaphore;
   123     Uint8 *mixbuf;
   124     int mixlen;
   125     Uint8 *nextbuf;
   126 };
   127 
   128 
   129 static void
   130 XAUDIO2_DetectDevices(void)
   131 {
   132     IXAudio2 *ixa2 = NULL;
   133     UINT32 devcount = 0;
   134     UINT32 i = 0;
   135 
   136     if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
   137         SDL_SetError("XAudio2: XAudio2Create() failed at detection.");
   138         return;
   139     } else if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
   140         SDL_SetError("XAudio2: IXAudio2::GetDeviceCount() failed.");
   141         IXAudio2_Release(ixa2);
   142         return;
   143     }
   144 
   145     for (i = 0; i < devcount; i++) {
   146         XAUDIO2_DEVICE_DETAILS details;
   147         if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) {
   148             char *str = WIN_StringToUTF8(details.DisplayName);
   149             if (str != NULL) {
   150                 SDL_AddAudioDevice(SDL_FALSE, str, (void *) ((size_t) i+1));
   151                 SDL_free(str);  /* SDL_AddAudioDevice made a copy of the string. */
   152             }
   153         }
   154     }
   155 
   156     IXAudio2_Release(ixa2);
   157 }
   158 
   159 static void STDMETHODCALLTYPE
   160 VoiceCBOnBufferEnd(THIS_ void *data)
   161 {
   162     /* Just signal the SDL audio thread and get out of XAudio2's way. */
   163     SDL_AudioDevice *this = (SDL_AudioDevice *) data;
   164     SDL_SemPost(this->hidden->semaphore);
   165 }
   166 
   167 static void STDMETHODCALLTYPE
   168 VoiceCBOnVoiceError(THIS_ void *data, HRESULT Error)
   169 {
   170     SDL_AudioDevice *this = (SDL_AudioDevice *) data;
   171     SDL_OpenedAudioDeviceDisconnected(this);
   172 }
   173 
   174 /* no-op callbacks... */
   175 static void STDMETHODCALLTYPE VoiceCBOnStreamEnd(THIS) {}
   176 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b) {}
   177 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassEnd(THIS) {}
   178 static void STDMETHODCALLTYPE VoiceCBOnBufferStart(THIS_ void *data) {}
   179 static void STDMETHODCALLTYPE VoiceCBOnLoopEnd(THIS_ void *data) {}
   180 
   181 
   182 static Uint8 *
   183 XAUDIO2_GetDeviceBuf(_THIS)
   184 {
   185     return this->hidden->nextbuf;
   186 }
   187 
   188 static void
   189 XAUDIO2_PlayDevice(_THIS)
   190 {
   191     XAUDIO2_BUFFER buffer;
   192     Uint8 *mixbuf = this->hidden->mixbuf;
   193     Uint8 *nextbuf = this->hidden->nextbuf;
   194     const int mixlen = this->hidden->mixlen;
   195     IXAudio2SourceVoice *source = this->hidden->source;
   196     HRESULT result = S_OK;
   197 
   198     if (!SDL_AtomicGet(&this->enabled)) { /* shutting down? */
   199         return;
   200     }
   201 
   202     /* Submit the next filled buffer */
   203     SDL_zero(buffer);
   204     buffer.AudioBytes = mixlen;
   205     buffer.pAudioData = nextbuf;
   206     buffer.pContext = this;
   207 
   208     if (nextbuf == mixbuf) {
   209         nextbuf += mixlen;
   210     } else {
   211         nextbuf = mixbuf;
   212     }
   213     this->hidden->nextbuf = nextbuf;
   214 
   215     result = IXAudio2SourceVoice_SubmitSourceBuffer(source, &buffer, NULL);
   216     if (result == XAUDIO2_E_DEVICE_INVALIDATED) {
   217         /* !!! FIXME: possibly disconnected or temporary lost. Recover? */
   218     }
   219 
   220     if (result != S_OK) {  /* uhoh, panic! */
   221         IXAudio2SourceVoice_FlushSourceBuffers(source);
   222         SDL_OpenedAudioDeviceDisconnected(this);
   223     }
   224 }
   225 
   226 static void
   227 XAUDIO2_WaitDevice(_THIS)
   228 {
   229     if (SDL_AtomicGet(&this->enabled)) {
   230         SDL_SemWait(this->hidden->semaphore);
   231     }
   232 }
   233 
   234 static void
   235 XAUDIO2_PrepareToClose(_THIS)
   236 {
   237     IXAudio2SourceVoice *source = this->hidden->source;
   238     if (source) {
   239         IXAudio2SourceVoice_Discontinuity(source);
   240     }
   241 }
   242 
   243 static void
   244 XAUDIO2_CloseDevice(_THIS)
   245 {
   246     IXAudio2 *ixa2 = this->hidden->ixa2;
   247     IXAudio2SourceVoice *source = this->hidden->source;
   248     IXAudio2MasteringVoice *mastering = this->hidden->mastering;
   249 
   250     if (source != NULL) {
   251         IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW);
   252         IXAudio2SourceVoice_FlushSourceBuffers(source);
   253         IXAudio2SourceVoice_DestroyVoice(source);
   254     }
   255     if (ixa2 != NULL) {
   256         IXAudio2_StopEngine(ixa2);
   257     }
   258     if (mastering != NULL) {
   259         IXAudio2MasteringVoice_DestroyVoice(mastering);
   260     }
   261     if (ixa2 != NULL) {
   262         IXAudio2_Release(ixa2);
   263     }
   264     if (this->hidden->semaphore != NULL) {
   265         SDL_DestroySemaphore(this->hidden->semaphore);
   266     }
   267 
   268     SDL_free(this->hidden->mixbuf);
   269     SDL_free(this->hidden);
   270 }
   271 
   272 static int
   273 XAUDIO2_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
   274 {
   275     HRESULT result = S_OK;
   276     WAVEFORMATEX waveformat;
   277     int valid_format = 0;
   278     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   279     IXAudio2 *ixa2 = NULL;
   280     IXAudio2SourceVoice *source = NULL;
   281 #if defined(SDL_XAUDIO2_WIN8)
   282     LPCWSTR devId = NULL;
   283 #else
   284     UINT32 devId = 0;  /* 0 == system default device. */
   285 #endif
   286 
   287     static IXAudio2VoiceCallbackVtbl callbacks_vtable = {
   288         VoiceCBOnVoiceProcessPassStart,
   289         VoiceCBOnVoiceProcessPassEnd,
   290         VoiceCBOnStreamEnd,
   291         VoiceCBOnBufferStart,
   292         VoiceCBOnBufferEnd,
   293         VoiceCBOnLoopEnd,
   294         VoiceCBOnVoiceError
   295     };
   296 
   297     static IXAudio2VoiceCallback callbacks = { &callbacks_vtable };
   298 
   299 #if defined(SDL_XAUDIO2_WIN8)
   300     /* !!! FIXME: hook up hotplugging. */
   301 #else
   302     if (handle != NULL) {  /* specific device requested? */
   303         /* -1 because we increment the original value to avoid NULL. */
   304         const size_t val = ((size_t) handle) - 1;
   305         devId = (UINT32) val;
   306     }
   307 #endif
   308 
   309     if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
   310         return SDL_SetError("XAudio2: XAudio2Create() failed at open.");
   311     }
   312 
   313     /*
   314     XAUDIO2_DEBUG_CONFIGURATION debugConfig;
   315     debugConfig.TraceMask = XAUDIO2_LOG_ERRORS; //XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_DETAIL | XAUDIO2_LOG_FUNC_CALLS | XAUDIO2_LOG_TIMING | XAUDIO2_LOG_LOCKS | XAUDIO2_LOG_MEMORY | XAUDIO2_LOG_STREAMING;
   316     debugConfig.BreakMask = XAUDIO2_LOG_ERRORS; //XAUDIO2_LOG_WARNINGS;
   317     debugConfig.LogThreadID = TRUE;
   318     debugConfig.LogFileline = TRUE;
   319     debugConfig.LogFunctionName = TRUE;
   320     debugConfig.LogTiming = TRUE;
   321     ixa2->SetDebugConfiguration(&debugConfig);
   322     */
   323 
   324     /* Initialize all variables that we clean on shutdown */
   325     this->hidden = (struct SDL_PrivateAudioData *)
   326         SDL_malloc((sizeof *this->hidden));
   327     if (this->hidden == NULL) {
   328         IXAudio2_Release(ixa2);
   329         return SDL_OutOfMemory();
   330     }
   331     SDL_zerop(this->hidden);
   332 
   333     this->hidden->ixa2 = ixa2;
   334     this->hidden->semaphore = SDL_CreateSemaphore(1);
   335     if (this->hidden->semaphore == NULL) {
   336         return SDL_SetError("XAudio2: CreateSemaphore() failed!");
   337     }
   338 
   339     while ((!valid_format) && (test_format)) {
   340         switch (test_format) {
   341         case AUDIO_U8:
   342         case AUDIO_S16:
   343         case AUDIO_S32:
   344         case AUDIO_F32:
   345             this->spec.format = test_format;
   346             valid_format = 1;
   347             break;
   348         }
   349         test_format = SDL_NextAudioFormat();
   350     }
   351 
   352     if (!valid_format) {
   353         return SDL_SetError("XAudio2: Unsupported audio format");
   354     }
   355 
   356     /* Update the fragment size as size in bytes */
   357     SDL_CalculateAudioSpec(&this->spec);
   358 
   359     /* We feed a Source, it feeds the Mastering, which feeds the device. */
   360     this->hidden->mixlen = this->spec.size;
   361     this->hidden->mixbuf = (Uint8 *) SDL_malloc(2 * this->hidden->mixlen);
   362     if (this->hidden->mixbuf == NULL) {
   363         return SDL_OutOfMemory();
   364     }
   365     this->hidden->nextbuf = this->hidden->mixbuf;
   366     SDL_memset(this->hidden->mixbuf, this->spec.silence, 2 * this->hidden->mixlen);
   367 
   368     /* We use XAUDIO2_DEFAULT_CHANNELS instead of this->spec.channels. On
   369        Xbox360, this means 5.1 output, but on Windows, it means "figure out
   370        what the system has." It might be preferable to let XAudio2 blast
   371        stereo output to appropriate surround sound configurations
   372        instead of clamping to 2 channels, even though we'll configure the
   373        Source Voice for whatever number of channels you supply. */
   374 #if SDL_XAUDIO2_WIN8
   375     result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
   376                                            XAUDIO2_DEFAULT_CHANNELS,
   377                                            this->spec.freq, 0, devId, NULL, AudioCategory_GameEffects);
   378 #else
   379     result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
   380                                            XAUDIO2_DEFAULT_CHANNELS,
   381                                            this->spec.freq, 0, devId, NULL);
   382 #endif
   383     if (result != S_OK) {
   384         return SDL_SetError("XAudio2: Couldn't create mastering voice");
   385     }
   386 
   387     SDL_zero(waveformat);
   388     if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
   389         waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
   390     } else {
   391         waveformat.wFormatTag = WAVE_FORMAT_PCM;
   392     }
   393     waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
   394     waveformat.nChannels = this->spec.channels;
   395     waveformat.nSamplesPerSec = this->spec.freq;
   396     waveformat.nBlockAlign =
   397         waveformat.nChannels * (waveformat.wBitsPerSample / 8);
   398     waveformat.nAvgBytesPerSec =
   399         waveformat.nSamplesPerSec * waveformat.nBlockAlign;
   400     waveformat.cbSize = sizeof(waveformat);
   401 
   402 #ifdef __WINRT__
   403     // DLudwig: for now, make XAudio2 do sample rate conversion, just to
   404     // get the loopwave test to work.
   405     //
   406     // TODO, WinRT: consider removing WinRT-specific source-voice creation code from SDL_xaudio2.c
   407     result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
   408                                         0,
   409                                         1.0f, &callbacks, NULL, NULL);
   410 #else
   411     result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
   412                                         XAUDIO2_VOICE_NOSRC |
   413                                         XAUDIO2_VOICE_NOPITCH,
   414                                         1.0f, &callbacks, NULL, NULL);
   415 
   416 #endif
   417     if (result != S_OK) {
   418         return SDL_SetError("XAudio2: Couldn't create source voice");
   419     }
   420     this->hidden->source = source;
   421 
   422     /* Start everything playing! */
   423     result = IXAudio2_StartEngine(ixa2);
   424     if (result != S_OK) {
   425         return SDL_SetError("XAudio2: Couldn't start engine");
   426     }
   427 
   428     result = IXAudio2SourceVoice_Start(source, 0, XAUDIO2_COMMIT_NOW);
   429     if (result != S_OK) {
   430         return SDL_SetError("XAudio2: Couldn't start source voice");
   431     }
   432 
   433     return 0; /* good to go. */
   434 }
   435 
   436 static void
   437 XAUDIO2_Deinitialize(void)
   438 {
   439 #if defined(__WIN32__)
   440     WIN_CoUninitialize();
   441 #endif
   442 }
   443 
   444 #endif  /* SDL_XAUDIO2_HAS_SDK */
   445 
   446 
   447 static int
   448 XAUDIO2_Init(SDL_AudioDriverImpl * impl)
   449 {
   450 #ifndef SDL_XAUDIO2_HAS_SDK
   451     SDL_SetError("XAudio2: SDL was built without XAudio2 support (old DirectX SDK).");
   452     return 0;  /* no XAudio2 support, ever. Update your SDK! */
   453 #else
   454     /* XAudio2Create() is a macro that uses COM; we don't load the .dll */
   455     IXAudio2 *ixa2 = NULL;
   456 #if defined(__WIN32__)
   457     // TODO, WinRT: Investigate using CoInitializeEx here
   458     if (FAILED(WIN_CoInitialize())) {
   459         SDL_SetError("XAudio2: CoInitialize() failed");
   460         return 0;
   461     }
   462 #endif
   463 
   464     if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
   465 #if defined(__WIN32__)
   466         WIN_CoUninitialize();
   467 #endif
   468         SDL_SetError("XAudio2: XAudio2Create() failed at initialization");
   469         return 0;  /* not available. */
   470     }
   471     IXAudio2_Release(ixa2);
   472 
   473     /* Set the function pointers */
   474     impl->DetectDevices = XAUDIO2_DetectDevices;
   475     impl->OpenDevice = XAUDIO2_OpenDevice;
   476     impl->PlayDevice = XAUDIO2_PlayDevice;
   477     impl->WaitDevice = XAUDIO2_WaitDevice;
   478     impl->PrepareToClose = XAUDIO2_PrepareToClose;
   479     impl->GetDeviceBuf = XAUDIO2_GetDeviceBuf;
   480     impl->CloseDevice = XAUDIO2_CloseDevice;
   481     impl->Deinitialize = XAUDIO2_Deinitialize;
   482 
   483     /* !!! FIXME: We can apparently use a C++ interface on Windows 8
   484      * !!! FIXME: (Windows::Devices::Enumeration::DeviceInformation) for device
   485      * !!! FIXME: detection, but it's not implemented here yet.
   486      * !!! FIXME:  see http://blogs.msdn.com/b/chuckw/archive/2012/04/02/xaudio2-and-windows-8-consumer-preview.aspx
   487      * !!! FIXME:  for now, force the default device.
   488      */
   489 #if defined(SDL_XAUDIO2_WIN8) || defined(__WINRT__)
   490     impl->OnlyHasDefaultOutputDevice = 1;
   491 #endif
   492 
   493     return 1;   /* this audio target is available. */
   494 #endif
   495 }
   496 
   497 AudioBootStrap XAUDIO2_bootstrap = {
   498     "xaudio2", "XAudio2", XAUDIO2_Init, 0
   499 };
   500 
   501 #endif  /* SDL_AUDIO_DRIVER_XAUDIO2 */
   502 
   503 /* vi: set ts=4 sw=4 expandtab: */