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