Commit 73153901714ae077c0d35c1be17139fd2eb8034c

Ryan C. Gordon 2016-08-06T02:47:27

audio: Implemented buffer queueing for capture devices (SDL_DequeueAudio()).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
diff --git a/include/SDL_audio.h b/include/SDL_audio.h
index 4f65521..d51f0d1 100644
--- a/include/SDL_audio.h
+++ b/include/SDL_audio.h
@@ -278,7 +278,8 @@ extern DECLSPEC const char *SDLCALL SDL_GetCurrentAudioDriver(void);
  *      protect data structures that it accesses by calling SDL_LockAudio()
  *      and SDL_UnlockAudio() in your code. Alternately, you may pass a NULL
  *      pointer here, and call SDL_QueueAudio() with some frequency, to queue
- *      more audio samples to be played.
+ *      more audio samples to be played (or for capture devices, call
+ *      SDL_DequeueAudio() with some frequency, to obtain audio samples).
  *    - \c desired->userdata is passed as the first parameter to your callback
  *      function. If you passed a NULL callback, this value is ignored.
  *
@@ -482,6 +483,10 @@ extern DECLSPEC void SDLCALL SDL_MixAudioFormat(Uint8 * dst,
 /**
  *  Queue more audio on non-callback devices.
  *
+ *  (If you are looking to retrieve queued audio from a non-callback capture
+ *  device, you want SDL_DequeueAudio() instead. This will return -1 to
+ *  signify an error if you use it with capture devices.)
+ *
  *  SDL offers two ways to feed audio to the device: you can either supply a
  *  callback that SDL triggers with some frequency to obtain more audio
  *  (pull method), or you can supply no callback, and then SDL will expect
@@ -517,20 +522,75 @@ extern DECLSPEC void SDLCALL SDL_MixAudioFormat(Uint8 * dst,
 extern DECLSPEC int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID dev, const void *data, Uint32 len);
 
 /**
+ *  Dequeue more audio on non-callback devices.
+ *
+ *  (If you are looking to queue audio for output on a non-callback playback
+ *  device, you want SDL_QueueAudio() instead. This will always return 0
+ *  if you use it with playback devices.)
+ *
+ *  SDL offers two ways to retrieve audio from a capture device: you can
+ *  either supply a callback that SDL triggers with some frequency as the
+ *  device records more audio data, (push method), or you can supply no
+ *  callback, and then SDL will expect you to retrieve data at regular
+ *  intervals (pull method) with this function.
+ *
+ *  There are no limits on the amount of data you can queue, short of
+ *  exhaustion of address space. Data from the device will keep queuing as
+ *  necessary without further intervention from you. This means you will
+ *  eventually run out of memory if you aren't routinely dequeueing data.
+ *
+ *  Capture devices will not queue data when paused; if you are expecting
+ *  to not need captured audio for some length of time, use
+ *  SDL_PauseAudioDevice() to stop the capture device from queueing more
+ *  data. This can be useful during, say, level loading times. When
+ *  unpaused, capture devices will start queueing data from that point,
+ *  having flushed any capturable data available while paused.
+ *
+ *  This function is thread-safe, but dequeueing from the same device from
+ *  two threads at once does not promise which thread will dequeued data
+ *  first.
+ *
+ *  You may not dequeue audio from a device that is using an
+ *  application-supplied callback; doing so returns an error. You have to use
+ *  the audio callback, or dequeue audio with this function, but not both.
+ *
+ *  You should not call SDL_LockAudio() on the device before queueing; SDL
+ *  handles locking internally for this function.
+ *
+ *  \param dev The device ID from which we will dequeue audio.
+ *  \param data A pointer into where audio data should be copied.
+ *  \param len The number of bytes (not samples!) to which (data) points.
+ *  \return number of bytes dequeued, which could be less than requested.
+ *
+ *  \sa SDL_GetQueuedAudioSize
+ *  \sa SDL_ClearQueuedAudio
+ */
+extern DECLSPEC Uint32 SDLCALL SDL_DequeueAudio(SDL_AudioDeviceID dev, void *data, Uint32 len);
+
+/**
  *  Get the number of bytes of still-queued audio.
  *
- *  This is the number of bytes that have been queued for playback with
- *  SDL_QueueAudio(), but have not yet been sent to the hardware.
+ *  For playback device:
+ *
+ *    This is the number of bytes that have been queued for playback with
+ *    SDL_QueueAudio(), but have not yet been sent to the hardware. This
+ *    number may shrink at any time, so this only informs of pending data.
+ *
+ *    Once we've sent it to the hardware, this function can not decide the
+ *    exact byte boundary of what has been played. It's possible that we just
+ *    gave the hardware several kilobytes right before you called this
+ *    function, but it hasn't played any of it yet, or maybe half of it, etc.
+ *
+ *  For capture devices:
  *
- *  Once we've sent it to the hardware, this function can not decide the exact
- *  byte boundary of what has been played. It's possible that we just gave the
- *  hardware several kilobytes right before you called this function, but it
- *  hasn't played any of it yet, or maybe half of it, etc.
+ *    This is the number of bytes that have been captured by the device and
+ *    are waiting for you to dequeue. This number may grow at any time, so
+ *    this only informs of the lower-bound of available data.
  *
  *  You may not queue audio on a device that is using an application-supplied
  *  callback; calling this function on such a device always returns 0.
- *  You have to use the audio callback or queue audio with SDL_QueueAudio(),
- *  but not both.
+ *  You have to queue audio with SDL_QueueAudio()/SDL_DequeueAudio(), or use
+ *  the audio callback, but not both.
  *
  *  You should not call SDL_LockAudio() on the device before querying; SDL
  *  handles locking internally for this function.
@@ -544,10 +604,17 @@ extern DECLSPEC int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID dev, const void *da
 extern DECLSPEC Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID dev);
 
 /**
- *  Drop any queued audio data waiting to be sent to the hardware.
+ *  Drop any queued audio data. For playback devices, this is any queued data
+ *  still waiting to be submitted to the hardware. For capture devices, this
+ *  is any data that was queued by the device that hasn't yet been dequeued by
+ *  the application.
  *
- *  Immediately after this call, SDL_GetQueuedAudioSize() will return 0 and
- *  the hardware will start playing silence if more audio isn't queued.
+ *  Immediately after this call, SDL_GetQueuedAudioSize() will return 0. For
+ *  playback devices, the hardware will start playing silence if more audio
+ *  isn't queued. Unpaused capture devices will start filling the queue again
+ *  as soon as they have more data available (which, depending on the state
+ *  of the hardware and the thread, could be before this function call
+ *  returns!).
  *
  *  This will not prevent playback of queued audio that's already been sent
  *  to the hardware, as we can not undo that, so expect there to be some
@@ -557,8 +624,8 @@ extern DECLSPEC Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID dev);
  *
  *  You may not queue audio on a device that is using an application-supplied
  *  callback; calling this function on such a device is always a no-op.
- *  You have to use the audio callback or queue audio with SDL_QueueAudio(),
- *  but not both.
+ *  You have to queue audio with SDL_QueueAudio()/SDL_DequeueAudio(), or use
+ *  the audio callback, but not both.
  *
  *  You should not call SDL_LockAudio() on the device before clearing the
  *  queue; SDL handles locking internally for this function.
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index ec3817b..a57ed2e 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -433,77 +433,24 @@ SDL_RemoveAudioDevice(const int iscapture, void *handle)
 
 /* this expects that you managed thread safety elsewhere. */
 static void
-free_audio_queue(SDL_AudioBufferQueue *buffer)
+free_audio_queue(SDL_AudioBufferQueue *packet)
 {
-    while (buffer) {
-        SDL_AudioBufferQueue *next = buffer->next;
-        SDL_free(buffer);
-        buffer = next;
+    while (packet) {
+        SDL_AudioBufferQueue *next = packet->next;
+        SDL_free(packet);
+        packet = next;
     }
 }
 
-static void SDLCALL
-SDL_BufferQueueDrainCallback(void *userdata, Uint8 *stream, int _len)
-{
-    /* this function always holds the mixer lock before being called. */
-    Uint32 len = (Uint32) _len;
-    SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
-    SDL_AudioBufferQueue *buffer;
-
-    SDL_assert(device != NULL);  /* this shouldn't ever happen, right?! */
-    SDL_assert(_len >= 0);  /* this shouldn't ever happen, right?! */
-
-    while ((len > 0) && ((buffer = device->buffer_queue_head) != NULL)) {
-        const Uint32 avail = buffer->datalen - buffer->startpos;
-        const Uint32 cpy = SDL_min(len, avail);
-        SDL_assert(device->queued_bytes >= avail);
-
-        SDL_memcpy(stream, buffer->data + buffer->startpos, cpy);
-        buffer->startpos += cpy;
-        stream += cpy;
-        device->queued_bytes -= cpy;
-        len -= cpy;
-
-        if (buffer->startpos == buffer->datalen) {  /* packet is done, put it in the pool. */
-            device->buffer_queue_head = buffer->next;
-            SDL_assert((buffer->next != NULL) || (buffer == device->buffer_queue_tail));
-            buffer->next = device->buffer_queue_pool;
-            device->buffer_queue_pool = buffer;
-        }
-    }
-
-    SDL_assert((device->buffer_queue_head != NULL) == (device->queued_bytes != 0));
-
-    if (len > 0) {  /* fill any remaining space in the stream with silence. */
-        SDL_assert(device->buffer_queue_head == NULL);
-        SDL_memset(stream, device->spec.silence, len);
-    }
-
-    if (device->buffer_queue_head == NULL) {
-        device->buffer_queue_tail = NULL;  /* in case we drained the queue entirely. */
-    }
-}
-
-int
-SDL_QueueAudio(SDL_AudioDeviceID devid, const void *_data, Uint32 len)
+/* NOTE: This assumes you'll hold the mixer lock before calling! */
+static int
+queue_audio_to_device(SDL_AudioDevice *device, const Uint8 *data, Uint32 len)
 {
-    SDL_AudioDevice *device = get_audio_device(devid);
-    const Uint8 *data = (const Uint8 *) _data;
     SDL_AudioBufferQueue *orighead;
     SDL_AudioBufferQueue *origtail;
     Uint32 origlen;
     Uint32 datalen;
 
-    if (!device) {
-        return -1;  /* get_audio_device() will have set the error state */
-    }
-
-    if (device->spec.callback != SDL_BufferQueueDrainCallback) {
-        return SDL_SetError("Audio device has a callback, queueing not allowed");
-    }
-
-    current_audio.impl.LockDevice(device);
-
     orighead = device->buffer_queue_head;
     origtail = device->buffer_queue_tail;
     origlen = origtail ? origtail->datalen : 0;
@@ -533,8 +480,6 @@ SDL_QueueAudio(SDL_AudioDeviceID devid, const void *_data, Uint32 len)
                     device->buffer_queue_tail = origtail;
                     device->buffer_queue_pool = NULL;
 
-                    current_audio.impl.UnlockDevice(device);
-
                     free_audio_queue(packet);  /* give back what we can. */
 
                     return SDL_OutOfMemory();
@@ -561,22 +506,142 @@ SDL_QueueAudio(SDL_AudioDeviceID devid, const void *_data, Uint32 len)
         device->queued_bytes += datalen;
     }
 
-    current_audio.impl.UnlockDevice(device);
-
     return 0;
 }
 
+/* NOTE: This assumes you'll hold the mixer lock before calling! */
+static Uint32
+dequeue_audio_from_device(SDL_AudioDevice *device, Uint8 *stream, Uint32 len)
+{
+    SDL_AudioBufferQueue *packet;
+    Uint8 *ptr = stream;
+
+    while ((len > 0) && ((packet = device->buffer_queue_head) != NULL)) {
+        const Uint32 avail = packet->datalen - packet->startpos;
+        const Uint32 cpy = SDL_min(len, avail);
+        SDL_assert(device->queued_bytes >= avail);
+
+        SDL_memcpy(ptr, packet->data + packet->startpos, cpy);
+        packet->startpos += cpy;
+        ptr += cpy;
+        device->queued_bytes -= cpy;
+        len -= cpy;
+
+        if (packet->startpos == packet->datalen) {  /* packet is done, put it in the pool. */
+            device->buffer_queue_head = packet->next;
+            SDL_assert((packet->next != NULL) || (packet == device->buffer_queue_tail));
+            packet->next = device->buffer_queue_pool;
+            device->buffer_queue_pool = packet;
+        }
+    }
+
+    SDL_assert((device->buffer_queue_head != NULL) == (device->queued_bytes != 0));
+
+    if (device->buffer_queue_head == NULL) {
+        device->buffer_queue_tail = NULL;  /* in case we drained the queue entirely. */
+    }
+
+    return (Uint32) (ptr - stream);
+}
+
+static void SDLCALL
+SDL_BufferQueueDrainCallback(void *userdata, Uint8 *stream, int len)
+{
+    /* this function always holds the mixer lock before being called. */
+    SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
+    Uint32 written;
+
+    SDL_assert(device != NULL);  /* this shouldn't ever happen, right?! */
+    SDL_assert(!device->iscapture);  /* this shouldn't ever happen, right?! */
+    SDL_assert(len >= 0);  /* this shouldn't ever happen, right?! */
+
+    written = dequeue_audio_from_device(device, stream, (Uint32) len);
+    stream += written;
+    len -= (int) written;
+
+    if (len > 0) {  /* fill any remaining space in the stream with silence. */
+        SDL_assert(device->buffer_queue_head == NULL);
+        SDL_memset(stream, device->spec.silence, len);
+    }
+}
+
+static void SDLCALL
+SDL_BufferQueueFillCallback(void *userdata, Uint8 *stream, int len)
+{
+    /* this function always holds the mixer lock before being called. */
+    SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
+
+    SDL_assert(device != NULL);  /* this shouldn't ever happen, right?! */
+    SDL_assert(device->iscapture);  /* this shouldn't ever happen, right?! */
+    SDL_assert(len >= 0);  /* this shouldn't ever happen, right?! */
+
+    /* note that if this needs to allocate more space and run out of memory,
+       we have no choice but to quietly drop the data and hope it works out
+       later, but you probably have bigger problems in this case anyhow. */
+    queue_audio_to_device(device, stream, (Uint32) len);
+}
+
+int
+SDL_QueueAudio(SDL_AudioDeviceID devid, const void *data, Uint32 len)
+{
+    SDL_AudioDevice *device = get_audio_device(devid);
+    int rc = 0;
+
+    if (!device) {
+        return -1;  /* get_audio_device() will have set the error state */
+    } else if (device->iscapture) {
+        return SDL_SetError("This is a capture device, queueing not allowed");
+    } else if (device->spec.callback != SDL_BufferQueueDrainCallback) {
+        return SDL_SetError("Audio device has a callback, queueing not allowed");
+    }
+
+    if (len > 0) {
+        current_audio.impl.LockDevice(device);
+        rc = queue_audio_to_device(device, data, len);
+        current_audio.impl.UnlockDevice(device);
+    }
+
+    return rc;
+}
+
+Uint32
+SDL_DequeueAudio(SDL_AudioDeviceID devid, void *data, Uint32 len)
+{
+    SDL_AudioDevice *device = get_audio_device(devid);
+    Uint32 rc;
+
+    if ( (len == 0) ||  /* nothing to do? */
+         (!device) ||  /* called with bogus device id */
+         (!device->iscapture) ||  /* playback devices can't dequeue */
+         (device->spec.callback != SDL_BufferQueueFillCallback) ) { /* not set for queueing */
+        return 0;  /* just report zero bytes dequeued. */
+    }
+
+    current_audio.impl.LockDevice(device);
+    rc = dequeue_audio_from_device(device, data, len);
+    current_audio.impl.UnlockDevice(device);
+    return rc;
+}
+
 Uint32
 SDL_GetQueuedAudioSize(SDL_AudioDeviceID devid)
 {
     Uint32 retval = 0;
     SDL_AudioDevice *device = get_audio_device(devid);
 
+    if (!device) {
+        return 0;
+    }
+
     /* Nothing to do unless we're set up for queueing. */
-    if (device && (device->spec.callback == SDL_BufferQueueDrainCallback)) {
+    if (device->spec.callback == SDL_BufferQueueDrainCallback) {
         current_audio.impl.LockDevice(device);
         retval = device->queued_bytes + current_audio.impl.GetPendingBytes(device);
         current_audio.impl.UnlockDevice(device);
+    } else if (device->spec.callback == SDL_BufferQueueFillCallback) {
+        current_audio.impl.LockDevice(device);
+        retval = device->queued_bytes;
+        current_audio.impl.UnlockDevice(device);
     }
 
     return retval;
@@ -1305,7 +1370,7 @@ open_audio_device(const char *devname, int iscapture,
             }
         }
 
-        device->spec.callback = SDL_BufferQueueDrainCallback;
+        device->spec.callback = iscapture ? SDL_BufferQueueFillCallback : SDL_BufferQueueDrainCallback;
         device->spec.userdata = device;
     }
 
@@ -1319,7 +1384,9 @@ open_audio_device(const char *devname, int iscapture,
         /* !!! FIXME: we don't force the audio thread stack size here because it calls into user code, but maybe we should? */
         /* buffer queueing callback only needs a few bytes, so make the stack tiny. */
         char name[64];
-        const SDL_bool is_internal_thread = (device->spec.callback == SDL_BufferQueueDrainCallback);
+        const SDL_bool is_internal_thread =
+            (device->spec.callback == SDL_BufferQueueDrainCallback) ||
+            (device->spec.callback == SDL_BufferQueueFillCallback);
         const size_t stacksize = is_internal_thread ? 64 * 1024 : 0;
 
         SDL_snprintf(name, sizeof (name), "SDLAudioDev%d", (int) device->id);
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 50bbc35..dfde8e7 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -605,3 +605,4 @@
 #define SDL_SetWindowModalFor SDL_SetWindowModalFor_REAL
 #define SDL_RenderSetIntegerScale SDL_RenderSetIntegerScale_REAL
 #define SDL_RenderGetIntegerScale SDL_RenderGetIntegerScale_REAL
+#define SDL_DequeueAudio SDL_DequeueAudio_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index e73b0d9..dabda3d 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -639,3 +639,4 @@ SDL_DYNAPI_PROC(int,SDL_SetWindowInputFocus,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_SetWindowModalFor,(SDL_Window *a, SDL_Window *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_RenderSetIntegerScale,(SDL_Renderer *a, SDL_bool b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_RenderGetIntegerScale,(SDL_Renderer *a),(a),return)
+SDL_DYNAPI_PROC(Uint32,SDL_DequeueAudio,(SDL_AudioDeviceID a, void *b, Uint32 c),(a,b,c),return)