Backed out changeset ffd52bb02bcc
authorRyan C. Gordon <icculus@icculus.org>
Mon, 25 Mar 2019 12:24:38 -0400
changeset 12670dcb6c57df2fc
parent 12669 36ee99107390
child 12671 a7b99312d9f3
Backed out changeset ffd52bb02bcc

This was meant to migrate CoreAudio onto the same SDL_RunAudio() path that
most other audio drivers are on, but it introduced a bug because it doesn't
deal with dropped audio buffers...and fixing that properly just introduces
latency.

I might revisit this later, perhaps by reworking SDL_RunAudio to allow for
this sort of API better, or redesigning the whole subsystem or something, I
don't know. I'm not super-thrilled that this has to exist outside of the usual
codepaths, though.

Fixes Bugzilla #4481.
src/audio/SDL_audio.c
src/audio/coreaudio/SDL_coreaudio.h
src/audio/coreaudio/SDL_coreaudio.m
     1.1 --- a/src/audio/SDL_audio.c	Thu Mar 21 10:39:49 2019 -0400
     1.2 +++ b/src/audio/SDL_audio.c	Mon Mar 25 12:24:38 2019 -0400
     1.3 @@ -896,8 +896,6 @@
     1.4          }
     1.5      }
     1.6  
     1.7 -    current_audio.impl.PrepareToClose(device);
     1.8 -
     1.9      current_audio.impl.FlushCapture(device);
    1.10  
    1.11      current_audio.impl.ThreadDeinit(device);
     2.1 --- a/src/audio/coreaudio/SDL_coreaudio.h	Thu Mar 21 10:39:49 2019 -0400
     2.2 +++ b/src/audio/coreaudio/SDL_coreaudio.h	Mon Mar 25 12:24:38 2019 -0400
     2.3 @@ -45,14 +45,16 @@
     2.4  
     2.5  struct SDL_PrivateAudioData
     2.6  {
     2.7 +    SDL_Thread *thread;
     2.8      AudioQueueRef audioQueue;
     2.9 -    int numAudioBuffers;
    2.10      AudioQueueBufferRef *audioBuffer;
    2.11      void *buffer;
    2.12 +    UInt32 bufferOffset;
    2.13      UInt32 bufferSize;
    2.14      AudioStreamBasicDescription strdesc;
    2.15 -    SDL_bool refill;
    2.16 -    SDL_AudioStream *capturestream;
    2.17 +    SDL_sem *ready_semaphore;
    2.18 +    char *thread_error;
    2.19 +    SDL_atomic_t shutdown;
    2.20  #if MACOSX_COREAUDIO
    2.21      AudioDeviceID deviceID;
    2.22  #else
     3.1 --- a/src/audio/coreaudio/SDL_coreaudio.m	Thu Mar 21 10:39:49 2019 -0400
     3.2 +++ b/src/audio/coreaudio/SDL_coreaudio.m	Mon Mar 25 12:24:38 2019 -0400
     3.3 @@ -26,7 +26,6 @@
     3.4  
     3.5  #include "SDL_audio.h"
     3.6  #include "SDL_hints.h"
     3.7 -#include "SDL_timer.h"
     3.8  #include "../SDL_audio_c.h"
     3.9  #include "../SDL_sysaudio.h"
    3.10  #include "SDL_coreaudio.h"
    3.11 @@ -410,27 +409,43 @@
    3.12  outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
    3.13  {
    3.14      SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
    3.15 -    SDL_assert(inBuffer->mAudioDataBytesCapacity == this->hidden->bufferSize);
    3.16 -    SDL_memcpy(inBuffer->mAudioData, this->hidden->buffer, this->hidden->bufferSize);
    3.17 -    SDL_memset(this->hidden->buffer, '\0', this->hidden->bufferSize);  /* zero out in case we have to fill again without new data. */
    3.18 -    inBuffer->mAudioDataByteSize = this->hidden->bufferSize;
    3.19 -    AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
    3.20 -    this->hidden->refill = SDL_TRUE;
    3.21 -}
    3.22 +    if (SDL_AtomicGet(&this->hidden->shutdown)) {
    3.23 +        return;  /* don't do anything. */
    3.24 +    }
    3.25 +
    3.26 +    if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
    3.27 +        /* Supply silence if audio is not enabled or paused */
    3.28 +        SDL_memset(inBuffer->mAudioData, this->spec.silence, inBuffer->mAudioDataBytesCapacity);
    3.29 +    } else {
    3.30 +        UInt32 remaining = inBuffer->mAudioDataBytesCapacity;
    3.31 +        Uint8 *ptr = (Uint8 *) inBuffer->mAudioData;
    3.32  
    3.33 -static Uint8 *
    3.34 -COREAUDIO_GetDeviceBuf(_THIS)
    3.35 -{
    3.36 -    return this->hidden->buffer;
    3.37 -}
    3.38 +        while (remaining > 0) {
    3.39 +            UInt32 len;
    3.40 +            if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
    3.41 +                /* Generate the data */
    3.42 +                SDL_LockMutex(this->mixer_lock);
    3.43 +                (*this->callbackspec.callback)(this->callbackspec.userdata,
    3.44 +                            this->hidden->buffer, this->hidden->bufferSize);
    3.45 +                SDL_UnlockMutex(this->mixer_lock);
    3.46 +                this->hidden->bufferOffset = 0;
    3.47 +            }
    3.48  
    3.49 -static void
    3.50 -COREAUDIO_WaitDevice(_THIS)
    3.51 -{
    3.52 -    while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) {
    3.53 -        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
    3.54 +            len = this->hidden->bufferSize - this->hidden->bufferOffset;
    3.55 +            if (len > remaining) {
    3.56 +                len = remaining;
    3.57 +            }
    3.58 +            SDL_memcpy(ptr, (char *)this->hidden->buffer +
    3.59 +                       this->hidden->bufferOffset, len);
    3.60 +            ptr = ptr + len;
    3.61 +            remaining -= len;
    3.62 +            this->hidden->bufferOffset += len;
    3.63 +        }
    3.64      }
    3.65 -    this->hidden->refill = SDL_FALSE;
    3.66 +
    3.67 +    AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
    3.68 +
    3.69 +    inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity;
    3.70  }
    3.71  
    3.72  static void
    3.73 @@ -439,46 +454,36 @@
    3.74                const AudioStreamPacketDescription *inPacketDescs )
    3.75  {
    3.76      SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
    3.77 -    if (SDL_AtomicGet(&this->enabled)) {
    3.78 -        SDL_AudioStream *stream = this->hidden->capturestream;
    3.79 -        if (SDL_AudioStreamPut(stream, inBuffer->mAudioData, inBuffer->mAudioDataByteSize) == -1) {
    3.80 -            /* yikes, out of memory or something. I guess drop the buffer. Our WASAPI target kills the device in this case, though */
    3.81 -        }
    3.82 -        AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
    3.83 -        this->hidden->refill = SDL_TRUE;
    3.84 -    }
    3.85 -}
    3.86  
    3.87 -static int
    3.88 -COREAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen)
    3.89 -{
    3.90 -    SDL_AudioStream *stream = this->hidden->capturestream;
    3.91 -    while (SDL_AtomicGet(&this->enabled)) {
    3.92 -        const int avail = SDL_AudioStreamAvailable(stream);
    3.93 -        if (avail > 0) {
    3.94 -            const int cpy = SDL_min(buflen, avail);
    3.95 -            SDL_AudioStreamGet(stream, buffer, cpy);
    3.96 -            return cpy;
    3.97 -        }
    3.98 -
    3.99 -        /* wait for more data, try again. */
   3.100 -        while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) {
   3.101 -            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
   3.102 -        }
   3.103 -        this->hidden->refill = SDL_FALSE;
   3.104 +    if (SDL_AtomicGet(&this->shutdown)) {
   3.105 +        return;  /* don't do anything. */
   3.106      }
   3.107  
   3.108 -    return 0;  /* not enabled, giving up. */
   3.109 -}
   3.110 +    /* ignore unless we're active. */
   3.111 +    if (!SDL_AtomicGet(&this->paused) && SDL_AtomicGet(&this->enabled) && !SDL_AtomicGet(&this->paused)) {
   3.112 +        const Uint8 *ptr = (const Uint8 *) inBuffer->mAudioData;
   3.113 +        UInt32 remaining = inBuffer->mAudioDataByteSize;
   3.114 +        while (remaining > 0) {
   3.115 +            UInt32 len = this->hidden->bufferSize - this->hidden->bufferOffset;
   3.116 +            if (len > remaining) {
   3.117 +                len = remaining;
   3.118 +            }
   3.119  
   3.120 -static void
   3.121 -COREAUDIO_FlushCapture(_THIS)
   3.122 -{
   3.123 -    while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 1) == kCFRunLoopRunHandledSource) {
   3.124 -        /* spin. */
   3.125 +            SDL_memcpy((char *)this->hidden->buffer + this->hidden->bufferOffset, ptr, len);
   3.126 +            ptr += len;
   3.127 +            remaining -= len;
   3.128 +            this->hidden->bufferOffset += len;
   3.129 +
   3.130 +            if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
   3.131 +                SDL_LockMutex(this->mixer_lock);
   3.132 +                (*this->callbackspec.callback)(this->callbackspec.userdata, this->hidden->buffer, this->hidden->bufferSize);
   3.133 +                SDL_UnlockMutex(this->mixer_lock);
   3.134 +                this->hidden->bufferOffset = 0;
   3.135 +            }
   3.136 +        }
   3.137      }
   3.138 -    this->hidden->refill = SDL_FALSE;
   3.139 -    SDL_AudioStreamClear(this->hidden->capturestream);
   3.140 +
   3.141 +    AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
   3.142  }
   3.143  
   3.144  
   3.145 @@ -536,16 +541,25 @@
   3.146      update_audio_session(this, SDL_FALSE);
   3.147  #endif
   3.148  
   3.149 +    /* if callback fires again, feed silence; don't call into the app. */
   3.150 +    SDL_AtomicSet(&this->paused, 1);
   3.151 +
   3.152      if (this->hidden->audioQueue) {
   3.153          AudioQueueDispose(this->hidden->audioQueue, 1);
   3.154      }
   3.155  
   3.156 -    if (this->hidden->capturestream) {
   3.157 -        SDL_FreeAudioStream(this->hidden->capturestream);
   3.158 +    if (this->hidden->thread) {
   3.159 +        SDL_AtomicSet(&this->hidden->shutdown, 1);
   3.160 +        SDL_WaitThread(this->hidden->thread, NULL);
   3.161 +    }
   3.162 +
   3.163 +    if (this->hidden->ready_semaphore) {
   3.164 +        SDL_DestroySemaphore(this->hidden->ready_semaphore);
   3.165      }
   3.166  
   3.167      /* AudioQueueDispose() frees the actual buffer objects. */
   3.168      SDL_free(this->hidden->audioBuffer);
   3.169 +    SDL_free(this->hidden->thread_error);
   3.170      SDL_free(this->hidden->buffer);
   3.171      SDL_free(this->hidden);
   3.172  
   3.173 @@ -611,8 +625,6 @@
   3.174  }
   3.175  #endif
   3.176  
   3.177 -
   3.178 -/* this all happens in the audio thread, since it needs a separate runloop. */
   3.179  static int
   3.180  prepare_audioqueue(_THIS)
   3.181  {
   3.182 @@ -652,6 +664,19 @@
   3.183  }
   3.184  #endif
   3.185  
   3.186 +    /* Calculate the final parameters for this audio specification */
   3.187 +    SDL_CalculateAudioSpec(&this->spec);
   3.188 +
   3.189 +    /* Allocate a sample buffer */
   3.190 +    this->hidden->bufferSize = this->spec.size;
   3.191 +    this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize;
   3.192 +
   3.193 +    this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
   3.194 +    if (this->hidden->buffer == NULL) {
   3.195 +        SDL_OutOfMemory();
   3.196 +        return 0;
   3.197 +    }
   3.198 +
   3.199      /* Make sure we can feed the device a minimum amount of time */
   3.200      double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
   3.201  #if defined(__IPHONEOS__)
   3.202 @@ -666,7 +691,6 @@
   3.203          numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
   3.204      }
   3.205  
   3.206 -    this->hidden->numAudioBuffers = numAudioBuffers;
   3.207      this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers);
   3.208      if (this->hidden->audioBuffer == NULL) {
   3.209          SDL_OutOfMemory();
   3.210 @@ -693,23 +717,29 @@
   3.211      return 1;
   3.212  }
   3.213  
   3.214 -static void
   3.215 -COREAUDIO_ThreadInit(_THIS)
   3.216 +static int
   3.217 +audioqueue_thread(void *arg)
   3.218  {
   3.219 +    SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
   3.220      const int rc = prepare_audioqueue(this);
   3.221      if (!rc) {
   3.222 -        /* !!! FIXME: do this in RunAudio, and maybe block OpenDevice until ThreadInit finishes, too, to report an opening error */
   3.223 -        SDL_OpenedAudioDeviceDisconnected(this);  /* oh well. */
   3.224 +        this->hidden->thread_error = SDL_strdup(SDL_GetError());
   3.225 +        SDL_SemPost(this->hidden->ready_semaphore);
   3.226 +        return 0;
   3.227      }
   3.228 -}
   3.229  
   3.230 -static void
   3.231 -COREAUDIO_PrepareToClose(_THIS)
   3.232 -{
   3.233 -    /* run long enough to queue some silence, so we know our actual audio
   3.234 -       has been played */
   3.235 -    CFRunLoopRunInMode(kCFRunLoopDefaultMode, (((this->spec.samples * 1000) / this->spec.freq) * 2) / 1000.0f, 0);
   3.236 -    AudioQueueStop(this->hidden->audioQueue, 1);
   3.237 +    /* init was successful, alert parent thread and start running... */
   3.238 +    SDL_SemPost(this->hidden->ready_semaphore);
   3.239 +    while (!SDL_AtomicGet(&this->hidden->shutdown)) {
   3.240 +        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
   3.241 +    }
   3.242 +
   3.243 +    if (!this->iscapture) {  /* Drain off any pending playback. */
   3.244 +        const CFTimeInterval secs = (((this->spec.size / (SDL_AUDIO_BITSIZE(this->spec.format) / 8)) / this->spec.channels) / ((CFTimeInterval) this->spec.freq)) * 2.0;
   3.245 +        CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0);
   3.246 +    }
   3.247 +
   3.248 +    return 0;
   3.249  }
   3.250  
   3.251  static int
   3.252 @@ -796,23 +826,28 @@
   3.253      }
   3.254  #endif
   3.255  
   3.256 -    /* Calculate the final parameters for this audio specification */
   3.257 -    SDL_CalculateAudioSpec(&this->spec);
   3.258 -
   3.259 -    if (iscapture) {
   3.260 -        this->hidden->capturestream = SDL_NewAudioStream(this->spec.format, this->spec.channels, this->spec.freq, this->spec.format, this->spec.channels, this->spec.freq);
   3.261 -        if (!this->hidden->capturestream) {
   3.262 -            return -1;  /* already set SDL_Error */
   3.263 -        }
   3.264 -    } else {
   3.265 -        this->hidden->bufferSize = this->spec.size;
   3.266 -        this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
   3.267 -        if (this->hidden->buffer == NULL) {
   3.268 -            return SDL_OutOfMemory();
   3.269 -        }
   3.270 +    /* This has to init in a new thread so it can get its own CFRunLoop. :/ */
   3.271 +    SDL_AtomicSet(&this->hidden->shutdown, 0);
   3.272 +    this->hidden->ready_semaphore = SDL_CreateSemaphore(0);
   3.273 +    if (!this->hidden->ready_semaphore) {
   3.274 +        return -1;  /* oh well. */
   3.275      }
   3.276  
   3.277 -    return 0;
   3.278 +    this->hidden->thread = SDL_CreateThreadInternal(audioqueue_thread, "AudioQueue thread", 512 * 1024, this);
   3.279 +    if (!this->hidden->thread) {
   3.280 +        return -1;
   3.281 +    }
   3.282 +
   3.283 +    SDL_SemWait(this->hidden->ready_semaphore);
   3.284 +    SDL_DestroySemaphore(this->hidden->ready_semaphore);
   3.285 +    this->hidden->ready_semaphore = NULL;
   3.286 +
   3.287 +    if ((this->hidden->thread != NULL) && (this->hidden->thread_error != NULL)) {
   3.288 +        SDL_SetError("%s", this->hidden->thread_error);
   3.289 +        return -1;
   3.290 +    }
   3.291 +
   3.292 +    return (this->hidden->thread != NULL) ? 0 : -1;
   3.293  }
   3.294  
   3.295  static void
   3.296 @@ -832,12 +867,6 @@
   3.297      impl->OpenDevice = COREAUDIO_OpenDevice;
   3.298      impl->CloseDevice = COREAUDIO_CloseDevice;
   3.299      impl->Deinitialize = COREAUDIO_Deinitialize;
   3.300 -    impl->ThreadInit = COREAUDIO_ThreadInit;
   3.301 -    impl->WaitDevice = COREAUDIO_WaitDevice;
   3.302 -    impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf;
   3.303 -    impl->PrepareToClose = COREAUDIO_PrepareToClose;
   3.304 -    impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice;
   3.305 -    impl->FlushCapture = COREAUDIO_FlushCapture;
   3.306  
   3.307  #if MACOSX_COREAUDIO
   3.308      impl->DetectDevices = COREAUDIO_DetectDevices;
   3.309 @@ -847,6 +876,7 @@
   3.310      impl->OnlyHasDefaultCaptureDevice = 1;
   3.311  #endif
   3.312  
   3.313 +    impl->ProvidesOwnCallbackThread = 1;
   3.314      impl->HasCaptureSupport = 1;
   3.315  
   3.316      return 1;   /* this audio target is available. */