Ticket #58551: dcb6c57df2fc.txt

File dcb6c57df2fc.txt, 12.9 KB (added by gnubeest (beest), 5 years ago)

HG changeset patch (Ryan C. Gordon <icculus@…>)

Line 
1
2# HG changeset patch
3# User Ryan C. Gordon <icculus@icculus.org>
4# Date 1553531078 14400
5# Node ID dcb6c57df2fc8fbf5b82d04782322f64658a5e0b
6# Parent  36ee991073903add82a71be51943e3b650558c5a
7Backed out changeset ffd52bb02bcc
8
9This was meant to migrate CoreAudio onto the same SDL_RunAudio() path that
10most other audio drivers are on, but it introduced a bug because it doesn't
11deal with dropped audio buffers...and fixing that properly just introduces
12latency.
13
14I might revisit this later, perhaps by reworking SDL_RunAudio to allow for
15this sort of API better, or redesigning the whole subsystem or something, I
16don't know. I'm not super-thrilled that this has to exist outside of the usual
17codepaths, though.
18
19Fixes Bugzilla #4481.
20
21diff -r 36ee99107390 -r dcb6c57df2fc src/audio/SDL_audio.c
22--- a/src/audio/SDL_audio.c     Thu Mar 21 10:39:49 2019 -0400
23+++ b/src/audio/SDL_audio.c     Mon Mar 25 12:24:38 2019 -0400
24@@ -896,8 +896,6 @@
25         }
26     }
27 
28-    current_audio.impl.PrepareToClose(device);
29-
30     current_audio.impl.FlushCapture(device);
31 
32     current_audio.impl.ThreadDeinit(device);
33diff -r 36ee99107390 -r dcb6c57df2fc src/audio/coreaudio/SDL_coreaudio.h
34--- a/src/audio/coreaudio/SDL_coreaudio.h       Thu Mar 21 10:39:49 2019 -0400
35+++ b/src/audio/coreaudio/SDL_coreaudio.h       Mon Mar 25 12:24:38 2019 -0400
36@@ -45,14 +45,16 @@
37 
38 struct SDL_PrivateAudioData
39 {
40+    SDL_Thread *thread;
41     AudioQueueRef audioQueue;
42-    int numAudioBuffers;
43     AudioQueueBufferRef *audioBuffer;
44     void *buffer;
45+    UInt32 bufferOffset;
46     UInt32 bufferSize;
47     AudioStreamBasicDescription strdesc;
48-    SDL_bool refill;
49-    SDL_AudioStream *capturestream;
50+    SDL_sem *ready_semaphore;
51+    char *thread_error;
52+    SDL_atomic_t shutdown;
53 #if MACOSX_COREAUDIO
54     AudioDeviceID deviceID;
55 #else
56diff -r 36ee99107390 -r dcb6c57df2fc src/audio/coreaudio/SDL_coreaudio.m
57--- a/src/audio/coreaudio/SDL_coreaudio.m       Thu Mar 21 10:39:49 2019 -0400
58+++ b/src/audio/coreaudio/SDL_coreaudio.m       Mon Mar 25 12:24:38 2019 -0400
59@@ -26,7 +26,6 @@
60 
61 #include "SDL_audio.h"
62 #include "SDL_hints.h"
63-#include "SDL_timer.h"
64 #include "../SDL_audio_c.h"
65 #include "../SDL_sysaudio.h"
66 #include "SDL_coreaudio.h"
67@@ -410,27 +409,43 @@
68 outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
69 {
70     SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
71-    SDL_assert(inBuffer->mAudioDataBytesCapacity == this->hidden->bufferSize);
72-    SDL_memcpy(inBuffer->mAudioData, this->hidden->buffer, this->hidden->bufferSize);
73-    SDL_memset(this->hidden->buffer, '\0', this->hidden->bufferSize);  /* zero out in case we have to fill again without new data. */
74-    inBuffer->mAudioDataByteSize = this->hidden->bufferSize;
75+    if (SDL_AtomicGet(&this->hidden->shutdown)) {
76+        return;  /* don't do anything. */
77+    }
78+
79+    if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
80+        /* Supply silence if audio is not enabled or paused */
81+        SDL_memset(inBuffer->mAudioData, this->spec.silence, inBuffer->mAudioDataBytesCapacity);
82+    } else {
83+        UInt32 remaining = inBuffer->mAudioDataBytesCapacity;
84+        Uint8 *ptr = (Uint8 *) inBuffer->mAudioData;
85+
86+        while (remaining > 0) {
87+            UInt32 len;
88+            if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
89+                /* Generate the data */
90+                SDL_LockMutex(this->mixer_lock);
91+                (*this->callbackspec.callback)(this->callbackspec.userdata,
92+                            this->hidden->buffer, this->hidden->bufferSize);
93+                SDL_UnlockMutex(this->mixer_lock);
94+                this->hidden->bufferOffset = 0;
95+            }
96+
97+            len = this->hidden->bufferSize - this->hidden->bufferOffset;
98+            if (len > remaining) {
99+                len = remaining;
100+            }
101+            SDL_memcpy(ptr, (char *)this->hidden->buffer +
102+                       this->hidden->bufferOffset, len);
103+            ptr = ptr + len;
104+            remaining -= len;
105+            this->hidden->bufferOffset += len;
106+        }
107+    }
108+
109     AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
110-    this->hidden->refill = SDL_TRUE;
111-}
112 
113-static Uint8 *
114-COREAUDIO_GetDeviceBuf(_THIS)
115-{
116-    return this->hidden->buffer;
117-}
118-
119-static void
120-COREAUDIO_WaitDevice(_THIS)
121-{
122-    while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) {
123-        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
124-    }
125-    this->hidden->refill = SDL_FALSE;
126+    inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity;
127 }
128 
129 static void
130@@ -439,46 +454,36 @@
131               const AudioStreamPacketDescription *inPacketDescs )
132 {
133     SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
134-    if (SDL_AtomicGet(&this->enabled)) {
135-        SDL_AudioStream *stream = this->hidden->capturestream;
136-        if (SDL_AudioStreamPut(stream, inBuffer->mAudioData, inBuffer->mAudioDataByteSize) == -1) {
137-            /* yikes, out of memory or something. I guess drop the buffer. Our WASAPI target kills the device in this case, though */
138-        }
139-        AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
140-        this->hidden->refill = SDL_TRUE;
141-    }
142-}
143 
144-static int
145-COREAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen)
146-{
147-    SDL_AudioStream *stream = this->hidden->capturestream;
148-    while (SDL_AtomicGet(&this->enabled)) {
149-        const int avail = SDL_AudioStreamAvailable(stream);
150-        if (avail > 0) {
151-            const int cpy = SDL_min(buflen, avail);
152-            SDL_AudioStreamGet(stream, buffer, cpy);
153-            return cpy;
154-        }
155-
156-        /* wait for more data, try again. */
157-        while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) {
158-            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
159-        }
160-        this->hidden->refill = SDL_FALSE;
161+    if (SDL_AtomicGet(&this->shutdown)) {
162+        return;  /* don't do anything. */
163     }
164 
165-    return 0;  /* not enabled, giving up. */
166-}
167+    /* ignore unless we're active. */
168+    if (!SDL_AtomicGet(&this->paused) && SDL_AtomicGet(&this->enabled) && !SDL_AtomicGet(&this->paused)) {
169+        const Uint8 *ptr = (const Uint8 *) inBuffer->mAudioData;
170+        UInt32 remaining = inBuffer->mAudioDataByteSize;
171+        while (remaining > 0) {
172+            UInt32 len = this->hidden->bufferSize - this->hidden->bufferOffset;
173+            if (len > remaining) {
174+                len = remaining;
175+            }
176 
177-static void
178-COREAUDIO_FlushCapture(_THIS)
179-{
180-    while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 1) == kCFRunLoopRunHandledSource) {
181-        /* spin. */
182+            SDL_memcpy((char *)this->hidden->buffer + this->hidden->bufferOffset, ptr, len);
183+            ptr += len;
184+            remaining -= len;
185+            this->hidden->bufferOffset += len;
186+
187+            if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
188+                SDL_LockMutex(this->mixer_lock);
189+                (*this->callbackspec.callback)(this->callbackspec.userdata, this->hidden->buffer, this->hidden->bufferSize);
190+                SDL_UnlockMutex(this->mixer_lock);
191+                this->hidden->bufferOffset = 0;
192+            }
193+        }
194     }
195-    this->hidden->refill = SDL_FALSE;
196-    SDL_AudioStreamClear(this->hidden->capturestream);
197+
198+    AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
199 }
200 
201 
202@@ -536,16 +541,25 @@
203     update_audio_session(this, SDL_FALSE);
204 #endif
205 
206+    /* if callback fires again, feed silence; don't call into the app. */
207+    SDL_AtomicSet(&this->paused, 1);
208+
209     if (this->hidden->audioQueue) {
210         AudioQueueDispose(this->hidden->audioQueue, 1);
211     }
212 
213-    if (this->hidden->capturestream) {
214-        SDL_FreeAudioStream(this->hidden->capturestream);
215+    if (this->hidden->thread) {
216+        SDL_AtomicSet(&this->hidden->shutdown, 1);
217+        SDL_WaitThread(this->hidden->thread, NULL);
218+    }
219+
220+    if (this->hidden->ready_semaphore) {
221+        SDL_DestroySemaphore(this->hidden->ready_semaphore);
222     }
223 
224     /* AudioQueueDispose() frees the actual buffer objects. */
225     SDL_free(this->hidden->audioBuffer);
226+    SDL_free(this->hidden->thread_error);
227     SDL_free(this->hidden->buffer);
228     SDL_free(this->hidden);
229 
230@@ -611,8 +625,6 @@
231 }
232 #endif
233 
234-
235-/* this all happens in the audio thread, since it needs a separate runloop. */
236 static int
237 prepare_audioqueue(_THIS)
238 {
239@@ -652,6 +664,19 @@
240 }
241 #endif
242 
243+    /* Calculate the final parameters for this audio specification */
244+    SDL_CalculateAudioSpec(&this->spec);
245+
246+    /* Allocate a sample buffer */
247+    this->hidden->bufferSize = this->spec.size;
248+    this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize;
249+
250+    this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
251+    if (this->hidden->buffer == NULL) {
252+        SDL_OutOfMemory();
253+        return 0;
254+    }
255+
256     /* Make sure we can feed the device a minimum amount of time */
257     double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
258 #if defined(__IPHONEOS__)
259@@ -666,7 +691,6 @@
260         numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
261     }
262 
263-    this->hidden->numAudioBuffers = numAudioBuffers;
264     this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers);
265     if (this->hidden->audioBuffer == NULL) {
266         SDL_OutOfMemory();
267@@ -693,23 +717,29 @@
268     return 1;
269 }
270 
271-static void
272-COREAUDIO_ThreadInit(_THIS)
273+static int
274+audioqueue_thread(void *arg)
275 {
276+    SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
277     const int rc = prepare_audioqueue(this);
278     if (!rc) {
279-        /* !!! FIXME: do this in RunAudio, and maybe block OpenDevice until ThreadInit finishes, too, to report an opening error */
280-        SDL_OpenedAudioDeviceDisconnected(this);  /* oh well. */
281+        this->hidden->thread_error = SDL_strdup(SDL_GetError());
282+        SDL_SemPost(this->hidden->ready_semaphore);
283+        return 0;
284     }
285-}
286 
287-static void
288-COREAUDIO_PrepareToClose(_THIS)
289-{
290-    /* run long enough to queue some silence, so we know our actual audio
291-       has been played */
292-    CFRunLoopRunInMode(kCFRunLoopDefaultMode, (((this->spec.samples * 1000) / this->spec.freq) * 2) / 1000.0f, 0);
293-    AudioQueueStop(this->hidden->audioQueue, 1);
294+    /* init was successful, alert parent thread and start running... */
295+    SDL_SemPost(this->hidden->ready_semaphore);
296+    while (!SDL_AtomicGet(&this->hidden->shutdown)) {
297+        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
298+    }
299+
300+    if (!this->iscapture) {  /* Drain off any pending playback. */
301+        const CFTimeInterval secs = (((this->spec.size / (SDL_AUDIO_BITSIZE(this->spec.format) / 8)) / this->spec.channels) / ((CFTimeInterval) this->spec.freq)) * 2.0;
302+        CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0);
303+    }
304+
305+    return 0;
306 }
307 
308 static int
309@@ -796,23 +826,28 @@
310     }
311 #endif
312 
313-    /* Calculate the final parameters for this audio specification */
314-    SDL_CalculateAudioSpec(&this->spec);
315-
316-    if (iscapture) {
317-        this->hidden->capturestream = SDL_NewAudioStream(this->spec.format, this->spec.channels, this->spec.freq, this->spec.format, this->spec.channels, this->spec.freq);
318-        if (!this->hidden->capturestream) {
319-            return -1;  /* already set SDL_Error */
320-        }
321-    } else {
322-        this->hidden->bufferSize = this->spec.size;
323-        this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
324-        if (this->hidden->buffer == NULL) {
325-            return SDL_OutOfMemory();
326-        }
327+    /* This has to init in a new thread so it can get its own CFRunLoop. :/ */
328+    SDL_AtomicSet(&this->hidden->shutdown, 0);
329+    this->hidden->ready_semaphore = SDL_CreateSemaphore(0);
330+    if (!this->hidden->ready_semaphore) {
331+        return -1;  /* oh well. */
332     }
333 
334-    return 0;
335+    this->hidden->thread = SDL_CreateThreadInternal(audioqueue_thread, "AudioQueue thread", 512 * 1024, this);
336+    if (!this->hidden->thread) {
337+        return -1;
338+    }
339+
340+    SDL_SemWait(this->hidden->ready_semaphore);
341+    SDL_DestroySemaphore(this->hidden->ready_semaphore);
342+    this->hidden->ready_semaphore = NULL;
343+
344+    if ((this->hidden->thread != NULL) && (this->hidden->thread_error != NULL)) {
345+        SDL_SetError("%s", this->hidden->thread_error);
346+        return -1;
347+    }
348+
349+    return (this->hidden->thread != NULL) ? 0 : -1;
350 }
351 
352 static void
353@@ -832,12 +867,6 @@
354     impl->OpenDevice = COREAUDIO_OpenDevice;
355     impl->CloseDevice = COREAUDIO_CloseDevice;
356     impl->Deinitialize = COREAUDIO_Deinitialize;
357-    impl->ThreadInit = COREAUDIO_ThreadInit;
358-    impl->WaitDevice = COREAUDIO_WaitDevice;
359-    impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf;
360-    impl->PrepareToClose = COREAUDIO_PrepareToClose;
361-    impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice;
362-    impl->FlushCapture = COREAUDIO_FlushCapture;
363 
364 #if MACOSX_COREAUDIO
365     impl->DetectDevices = COREAUDIO_DetectDevices;
366@@ -847,6 +876,7 @@
367     impl->OnlyHasDefaultCaptureDevice = 1;
368 #endif
369 
370+    impl->ProvidesOwnCallbackThread = 1;
371     impl->HasCaptureSupport = 1;
372 
373     return 1;   /* this audio target is available. */
374