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