Backed out changeset cec31de4e126

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.
This commit is contained in:
Ryan C. Gordon 2019-03-25 12:24:38 -04:00
parent 8ab698af15
commit 6a3356ab3f
3 changed files with 129 additions and 99 deletions

View File

@ -896,8 +896,6 @@ SDL_CaptureAudio(void *devicep)
} }
} }
current_audio.impl.PrepareToClose(device);
current_audio.impl.FlushCapture(device); current_audio.impl.FlushCapture(device);
current_audio.impl.ThreadDeinit(device); current_audio.impl.ThreadDeinit(device);

View File

@ -45,14 +45,16 @@
struct SDL_PrivateAudioData struct SDL_PrivateAudioData
{ {
SDL_Thread *thread;
AudioQueueRef audioQueue; AudioQueueRef audioQueue;
int numAudioBuffers;
AudioQueueBufferRef *audioBuffer; AudioQueueBufferRef *audioBuffer;
void *buffer; void *buffer;
UInt32 bufferOffset;
UInt32 bufferSize; UInt32 bufferSize;
AudioStreamBasicDescription strdesc; AudioStreamBasicDescription strdesc;
SDL_bool refill; SDL_sem *ready_semaphore;
SDL_AudioStream *capturestream; char *thread_error;
SDL_atomic_t shutdown;
#if MACOSX_COREAUDIO #if MACOSX_COREAUDIO
AudioDeviceID deviceID; AudioDeviceID deviceID;
#else #else

View File

@ -26,7 +26,6 @@
#include "SDL_audio.h" #include "SDL_audio.h"
#include "SDL_hints.h" #include "SDL_hints.h"
#include "SDL_timer.h"
#include "../SDL_audio_c.h" #include "../SDL_audio_c.h"
#include "../SDL_sysaudio.h" #include "../SDL_sysaudio.h"
#include "SDL_coreaudio.h" #include "SDL_coreaudio.h"
@ -410,27 +409,43 @@ static void
outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
{ {
SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
SDL_assert(inBuffer->mAudioDataBytesCapacity == this->hidden->bufferSize); if (SDL_AtomicGet(&this->hidden->shutdown)) {
SDL_memcpy(inBuffer->mAudioData, this->hidden->buffer, this->hidden->bufferSize); return; /* don't do anything. */
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;
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;
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);
inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity;
} }
static void static void
@ -439,46 +454,36 @@ inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer
const AudioStreamPacketDescription *inPacketDescs ) const AudioStreamPacketDescription *inPacketDescs )
{ {
SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; 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 if (SDL_AtomicGet(&this->shutdown)) {
COREAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen) return; /* don't do anything. */
{
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;
} }
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 SDL_memcpy((char *)this->hidden->buffer + this->hidden->bufferOffset, ptr, len);
COREAUDIO_FlushCapture(_THIS) ptr += len;
{ remaining -= len;
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 1) == kCFRunLoopRunHandledSource) { this->hidden->bufferOffset += len;
/* spin. */
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 @@ COREAUDIO_CloseDevice(_THIS)
update_audio_session(this, SDL_FALSE); update_audio_session(this, SDL_FALSE);
#endif #endif
/* if callback fires again, feed silence; don't call into the app. */
SDL_AtomicSet(&this->paused, 1);
if (this->hidden->audioQueue) { if (this->hidden->audioQueue) {
AudioQueueDispose(this->hidden->audioQueue, 1); AudioQueueDispose(this->hidden->audioQueue, 1);
} }
if (this->hidden->capturestream) { if (this->hidden->thread) {
SDL_FreeAudioStream(this->hidden->capturestream); 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. */ /* AudioQueueDispose() frees the actual buffer objects. */
SDL_free(this->hidden->audioBuffer); SDL_free(this->hidden->audioBuffer);
SDL_free(this->hidden->thread_error);
SDL_free(this->hidden->buffer); SDL_free(this->hidden->buffer);
SDL_free(this->hidden); SDL_free(this->hidden);
@ -611,8 +625,6 @@ prepare_device(_THIS, void *handle, int iscapture)
} }
#endif #endif
/* this all happens in the audio thread, since it needs a separate runloop. */
static int static int
prepare_audioqueue(_THIS) prepare_audioqueue(_THIS)
{ {
@ -652,6 +664,19 @@ prepare_audioqueue(_THIS)
} }
#endif #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 */ /* Make sure we can feed the device a minimum amount of time */
double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
#if defined(__IPHONEOS__) #if defined(__IPHONEOS__)
@ -666,7 +691,6 @@ prepare_audioqueue(_THIS)
numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
} }
this->hidden->numAudioBuffers = numAudioBuffers;
this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers); this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers);
if (this->hidden->audioBuffer == NULL) { if (this->hidden->audioBuffer == NULL) {
SDL_OutOfMemory(); SDL_OutOfMemory();
@ -693,23 +717,29 @@ prepare_audioqueue(_THIS)
return 1; return 1;
} }
static void static int
COREAUDIO_ThreadInit(_THIS) audioqueue_thread(void *arg)
{ {
SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
const int rc = prepare_audioqueue(this); const int rc = prepare_audioqueue(this);
if (!rc) { if (!rc) {
/* !!! FIXME: do this in RunAudio, and maybe block OpenDevice until ThreadInit finishes, too, to report an opening error */ this->hidden->thread_error = SDL_strdup(SDL_GetError());
SDL_OpenedAudioDeviceDisconnected(this); /* oh well. */ SDL_SemPost(this->hidden->ready_semaphore);
return 0;
} }
}
static void /* init was successful, alert parent thread and start running... */
COREAUDIO_PrepareToClose(_THIS) SDL_SemPost(this->hidden->ready_semaphore);
{ while (!SDL_AtomicGet(&this->hidden->shutdown)) {
/* run long enough to queue some silence, so we know our actual audio CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
has been played */ }
CFRunLoopRunInMode(kCFRunLoopDefaultMode, (((this->spec.samples * 1000) / this->spec.freq) * 2) / 1000.0f, 0);
AudioQueueStop(this->hidden->audioQueue, 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 static int
@ -796,23 +826,28 @@ COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
} }
#endif #endif
/* Calculate the final parameters for this audio specification */ /* This has to init in a new thread so it can get its own CFRunLoop. :/ */
SDL_CalculateAudioSpec(&this->spec); SDL_AtomicSet(&this->hidden->shutdown, 0);
this->hidden->ready_semaphore = SDL_CreateSemaphore(0);
if (iscapture) { if (!this->hidden->ready_semaphore) {
this->hidden->capturestream = SDL_NewAudioStream(this->spec.format, this->spec.channels, this->spec.freq, this->spec.format, this->spec.channels, this->spec.freq); return -1; /* oh well. */
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();
}
} }
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 static void
@ -832,12 +867,6 @@ COREAUDIO_Init(SDL_AudioDriverImpl * impl)
impl->OpenDevice = COREAUDIO_OpenDevice; impl->OpenDevice = COREAUDIO_OpenDevice;
impl->CloseDevice = COREAUDIO_CloseDevice; impl->CloseDevice = COREAUDIO_CloseDevice;
impl->Deinitialize = COREAUDIO_Deinitialize; 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 #if MACOSX_COREAUDIO
impl->DetectDevices = COREAUDIO_DetectDevices; impl->DetectDevices = COREAUDIO_DetectDevices;
@ -847,6 +876,7 @@ COREAUDIO_Init(SDL_AudioDriverImpl * impl)
impl->OnlyHasDefaultCaptureDevice = 1; impl->OnlyHasDefaultCaptureDevice = 1;
#endif #endif
impl->ProvidesOwnCallbackThread = 1;
impl->HasCaptureSupport = 1; impl->HasCaptureSupport = 1;
return 1; /* this audio target is available. */ return 1; /* this audio target is available. */