# HG changeset patch # User Ryan C. Gordon # Date 1553531078 14400 # Node ID dcb6c57df2fc8fbf5b82d04782322f64658a5e0b # Parent 36ee991073903add82a71be51943e3b650558c5a 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. diff -r 36ee99107390 -r dcb6c57df2fc src/audio/SDL_audio.c --- a/src/audio/SDL_audio.c Thu Mar 21 10:39:49 2019 -0400 +++ b/src/audio/SDL_audio.c Mon Mar 25 12:24:38 2019 -0400 @@ -896,8 +896,6 @@ } } - current_audio.impl.PrepareToClose(device); - current_audio.impl.FlushCapture(device); current_audio.impl.ThreadDeinit(device); diff -r 36ee99107390 -r dcb6c57df2fc src/audio/coreaudio/SDL_coreaudio.h --- a/src/audio/coreaudio/SDL_coreaudio.h Thu Mar 21 10:39:49 2019 -0400 +++ b/src/audio/coreaudio/SDL_coreaudio.h Mon Mar 25 12:24:38 2019 -0400 @@ -45,14 +45,16 @@ struct SDL_PrivateAudioData { + SDL_Thread *thread; AudioQueueRef audioQueue; - int numAudioBuffers; AudioQueueBufferRef *audioBuffer; void *buffer; + UInt32 bufferOffset; UInt32 bufferSize; AudioStreamBasicDescription strdesc; - SDL_bool refill; - SDL_AudioStream *capturestream; + SDL_sem *ready_semaphore; + char *thread_error; + SDL_atomic_t shutdown; #if MACOSX_COREAUDIO AudioDeviceID deviceID; #else diff -r 36ee99107390 -r dcb6c57df2fc src/audio/coreaudio/SDL_coreaudio.m --- a/src/audio/coreaudio/SDL_coreaudio.m Thu Mar 21 10:39:49 2019 -0400 +++ b/src/audio/coreaudio/SDL_coreaudio.m Mon Mar 25 12:24:38 2019 -0400 @@ -26,7 +26,6 @@ #include "SDL_audio.h" #include "SDL_hints.h" -#include "SDL_timer.h" #include "../SDL_audio_c.h" #include "../SDL_sysaudio.h" #include "SDL_coreaudio.h" @@ -410,27 +409,43 @@ outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; - SDL_assert(inBuffer->mAudioDataBytesCapacity == this->hidden->bufferSize); - SDL_memcpy(inBuffer->mAudioData, this->hidden->buffer, this->hidden->bufferSize); - SDL_memset(this->hidden->buffer, '\0', this->hidden->bufferSize); /* zero out in case we have to fill again without new data. */ - inBuffer->mAudioDataByteSize = this->hidden->bufferSize; + if (SDL_AtomicGet(&this->hidden->shutdown)) { + return; /* don't do anything. */ + } + + if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) { + /* Supply silence if audio is not enabled or paused */ + SDL_memset(inBuffer->mAudioData, this->spec.silence, inBuffer->mAudioDataBytesCapacity); + } else { + UInt32 remaining = inBuffer->mAudioDataBytesCapacity; + Uint8 *ptr = (Uint8 *) inBuffer->mAudioData; + + while (remaining > 0) { + UInt32 len; + if (this->hidden->bufferOffset >= this->hidden->bufferSize) { + /* Generate the data */ + SDL_LockMutex(this->mixer_lock); + (*this->callbackspec.callback)(this->callbackspec.userdata, + this->hidden->buffer, this->hidden->bufferSize); + SDL_UnlockMutex(this->mixer_lock); + this->hidden->bufferOffset = 0; + } + + len = this->hidden->bufferSize - this->hidden->bufferOffset; + if (len > remaining) { + len = remaining; + } + SDL_memcpy(ptr, (char *)this->hidden->buffer + + this->hidden->bufferOffset, len); + ptr = ptr + len; + remaining -= len; + this->hidden->bufferOffset += len; + } + } + AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); - this->hidden->refill = SDL_TRUE; -} -static Uint8 * -COREAUDIO_GetDeviceBuf(_THIS) -{ - return this->hidden->buffer; -} - -static void -COREAUDIO_WaitDevice(_THIS) -{ - while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) { - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); - } - this->hidden->refill = SDL_FALSE; + inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity; } static void @@ -439,46 +454,36 @@ const AudioStreamPacketDescription *inPacketDescs ) { SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; - if (SDL_AtomicGet(&this->enabled)) { - SDL_AudioStream *stream = this->hidden->capturestream; - if (SDL_AudioStreamPut(stream, inBuffer->mAudioData, inBuffer->mAudioDataByteSize) == -1) { - /* yikes, out of memory or something. I guess drop the buffer. Our WASAPI target kills the device in this case, though */ - } - AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); - this->hidden->refill = SDL_TRUE; - } -} -static int -COREAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen) -{ - SDL_AudioStream *stream = this->hidden->capturestream; - while (SDL_AtomicGet(&this->enabled)) { - const int avail = SDL_AudioStreamAvailable(stream); - if (avail > 0) { - const int cpy = SDL_min(buflen, avail); - SDL_AudioStreamGet(stream, buffer, cpy); - return cpy; - } - - /* wait for more data, try again. */ - while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) { - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); - } - this->hidden->refill = SDL_FALSE; + if (SDL_AtomicGet(&this->shutdown)) { + return; /* don't do anything. */ } - return 0; /* not enabled, giving up. */ -} + /* ignore unless we're active. */ + if (!SDL_AtomicGet(&this->paused) && SDL_AtomicGet(&this->enabled) && !SDL_AtomicGet(&this->paused)) { + const Uint8 *ptr = (const Uint8 *) inBuffer->mAudioData; + UInt32 remaining = inBuffer->mAudioDataByteSize; + while (remaining > 0) { + UInt32 len = this->hidden->bufferSize - this->hidden->bufferOffset; + if (len > remaining) { + len = remaining; + } -static void -COREAUDIO_FlushCapture(_THIS) -{ - while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 1) == kCFRunLoopRunHandledSource) { - /* spin. */ + SDL_memcpy((char *)this->hidden->buffer + this->hidden->bufferOffset, ptr, len); + ptr += len; + remaining -= len; + this->hidden->bufferOffset += len; + + if (this->hidden->bufferOffset >= this->hidden->bufferSize) { + SDL_LockMutex(this->mixer_lock); + (*this->callbackspec.callback)(this->callbackspec.userdata, this->hidden->buffer, this->hidden->bufferSize); + SDL_UnlockMutex(this->mixer_lock); + this->hidden->bufferOffset = 0; + } + } } - this->hidden->refill = SDL_FALSE; - SDL_AudioStreamClear(this->hidden->capturestream); + + AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); } @@ -536,16 +541,25 @@ update_audio_session(this, SDL_FALSE); #endif + /* if callback fires again, feed silence; don't call into the app. */ + SDL_AtomicSet(&this->paused, 1); + if (this->hidden->audioQueue) { AudioQueueDispose(this->hidden->audioQueue, 1); } - if (this->hidden->capturestream) { - SDL_FreeAudioStream(this->hidden->capturestream); + if (this->hidden->thread) { + SDL_AtomicSet(&this->hidden->shutdown, 1); + SDL_WaitThread(this->hidden->thread, NULL); + } + + if (this->hidden->ready_semaphore) { + SDL_DestroySemaphore(this->hidden->ready_semaphore); } /* AudioQueueDispose() frees the actual buffer objects. */ SDL_free(this->hidden->audioBuffer); + SDL_free(this->hidden->thread_error); SDL_free(this->hidden->buffer); SDL_free(this->hidden); @@ -611,8 +625,6 @@ } #endif - -/* this all happens in the audio thread, since it needs a separate runloop. */ static int prepare_audioqueue(_THIS) { @@ -652,6 +664,19 @@ } #endif + /* Calculate the final parameters for this audio specification */ + SDL_CalculateAudioSpec(&this->spec); + + /* Allocate a sample buffer */ + this->hidden->bufferSize = this->spec.size; + this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize; + + this->hidden->buffer = SDL_malloc(this->hidden->bufferSize); + if (this->hidden->buffer == NULL) { + SDL_OutOfMemory(); + return 0; + } + /* Make sure we can feed the device a minimum amount of time */ double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; #if defined(__IPHONEOS__) @@ -666,7 +691,6 @@ numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); } - this->hidden->numAudioBuffers = numAudioBuffers; this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers); if (this->hidden->audioBuffer == NULL) { SDL_OutOfMemory(); @@ -693,23 +717,29 @@ return 1; } -static void -COREAUDIO_ThreadInit(_THIS) +static int +audioqueue_thread(void *arg) { + SDL_AudioDevice *this = (SDL_AudioDevice *) arg; const int rc = prepare_audioqueue(this); if (!rc) { - /* !!! FIXME: do this in RunAudio, and maybe block OpenDevice until ThreadInit finishes, too, to report an opening error */ - SDL_OpenedAudioDeviceDisconnected(this); /* oh well. */ + this->hidden->thread_error = SDL_strdup(SDL_GetError()); + SDL_SemPost(this->hidden->ready_semaphore); + return 0; } -} -static void -COREAUDIO_PrepareToClose(_THIS) -{ - /* run long enough to queue some silence, so we know our actual audio - has been played */ - CFRunLoopRunInMode(kCFRunLoopDefaultMode, (((this->spec.samples * 1000) / this->spec.freq) * 2) / 1000.0f, 0); - AudioQueueStop(this->hidden->audioQueue, 1); + /* init was successful, alert parent thread and start running... */ + SDL_SemPost(this->hidden->ready_semaphore); + while (!SDL_AtomicGet(&this->hidden->shutdown)) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); + } + + if (!this->iscapture) { /* Drain off any pending playback. */ + const CFTimeInterval secs = (((this->spec.size / (SDL_AUDIO_BITSIZE(this->spec.format) / 8)) / this->spec.channels) / ((CFTimeInterval) this->spec.freq)) * 2.0; + CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0); + } + + return 0; } static int @@ -796,23 +826,28 @@ } #endif - /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&this->spec); - - if (iscapture) { - this->hidden->capturestream = SDL_NewAudioStream(this->spec.format, this->spec.channels, this->spec.freq, this->spec.format, this->spec.channels, this->spec.freq); - if (!this->hidden->capturestream) { - return -1; /* already set SDL_Error */ - } - } else { - this->hidden->bufferSize = this->spec.size; - this->hidden->buffer = SDL_malloc(this->hidden->bufferSize); - if (this->hidden->buffer == NULL) { - return SDL_OutOfMemory(); - } + /* This has to init in a new thread so it can get its own CFRunLoop. :/ */ + SDL_AtomicSet(&this->hidden->shutdown, 0); + this->hidden->ready_semaphore = SDL_CreateSemaphore(0); + if (!this->hidden->ready_semaphore) { + return -1; /* oh well. */ } - return 0; + this->hidden->thread = SDL_CreateThreadInternal(audioqueue_thread, "AudioQueue thread", 512 * 1024, this); + if (!this->hidden->thread) { + return -1; + } + + SDL_SemWait(this->hidden->ready_semaphore); + SDL_DestroySemaphore(this->hidden->ready_semaphore); + this->hidden->ready_semaphore = NULL; + + if ((this->hidden->thread != NULL) && (this->hidden->thread_error != NULL)) { + SDL_SetError("%s", this->hidden->thread_error); + return -1; + } + + return (this->hidden->thread != NULL) ? 0 : -1; } static void @@ -832,12 +867,6 @@ impl->OpenDevice = COREAUDIO_OpenDevice; impl->CloseDevice = COREAUDIO_CloseDevice; impl->Deinitialize = COREAUDIO_Deinitialize; - impl->ThreadInit = COREAUDIO_ThreadInit; - impl->WaitDevice = COREAUDIO_WaitDevice; - impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf; - impl->PrepareToClose = COREAUDIO_PrepareToClose; - impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice; - impl->FlushCapture = COREAUDIO_FlushCapture; #if MACOSX_COREAUDIO impl->DetectDevices = COREAUDIO_DetectDevices; @@ -847,6 +876,7 @@ impl->OnlyHasDefaultCaptureDevice = 1; #endif + impl->ProvidesOwnCallbackThread = 1; impl->HasCaptureSupport = 1; return 1; /* this audio target is available. */