diff src/audio/SDL_audio.c @ 2716:f8f68f47285a

Final merge of Google Summer of Code 2008 work... Audio Ideas - Resampling and Pitch Shifting by Aaron Wishnick, mentored by Ryan C. Gordon
author Sam Lantinga <slouken@libsdl.org>
date Mon, 25 Aug 2008 15:08:59 +0000
parents aedfcdeb69b6
children 2768bd7281e0
line wrap: on
line diff
--- a/src/audio/SDL_audio.c	Mon Aug 25 10:14:21 2008 +0000
+++ b/src/audio/SDL_audio.c	Mon Aug 25 15:08:59 2008 +0000
@@ -256,6 +256,68 @@
 #undef FILL_STUB
 }
 
+/* Streaming functions (for when the input and output buffer sizes are different) */
+/* Write [length] bytes from buf into the streamer */
+void
+SDL_StreamWrite(SDL_AudioStreamer * stream, Uint8 * buf, int length)
+{
+    int i;
+
+    for (i = 0; i < length; ++i) {
+        stream->buffer[stream->write_pos] = buf[i];
+        ++stream->write_pos;
+    }
+}
+
+/* Read [length] bytes out of the streamer into buf */
+void
+SDL_StreamRead(SDL_AudioStreamer * stream, Uint8 * buf, int length)
+{
+    int i;
+
+    for (i = 0; i < length; ++i) {
+        buf[i] = stream->buffer[stream->read_pos];
+        ++stream->read_pos;
+    }
+}
+
+int
+SDL_StreamLength(SDL_AudioStreamer * stream)
+{
+    return (stream->write_pos - stream->read_pos) % stream->max_len;
+}
+
+/* Initialize the stream by allocating the buffer and setting the read/write heads to the beginning */
+int
+SDL_StreamInit(SDL_AudioStreamer * stream, int max_len, Uint8 silence)
+{
+    int i;
+
+    /* First try to allocate the buffer */
+    stream->buffer = (Uint8 *) malloc(max_len);
+    if (stream->buffer == NULL) {
+        return -1;
+    }
+
+    stream->max_len = max_len;
+    stream->read_pos = 0;
+    stream->write_pos = 0;
+
+    /* Zero out the buffer */
+    for (i = 0; i < max_len; ++i) {
+        stream->buffer[i] = silence;
+    }
+}
+
+/* Deinitialize the stream simply by freeing the buffer */
+void
+SDL_StreamDeinit(SDL_AudioStreamer * stream)
+{
+    if (stream->buffer != NULL) {
+        free(stream->buffer);
+    }
+}
+
 
 /* The general mixing thread function */
 int SDLCALL
@@ -267,6 +329,11 @@
     void *udata;
     void (SDLCALL * fill) (void *userdata, Uint8 * stream, int len);
     int silence;
+    int stream_max_len;
+
+    /* For streaming when the buffer sizes don't match up */
+    Uint8 *istream;
+    int istream_len;
 
     /* Perform any thread setup */
     device->threadid = SDL_ThreadID();
@@ -276,67 +343,188 @@
     fill = device->spec.callback;
     udata = device->spec.userdata;
 
+    /* By default do not stream */
+    device->use_streamer = 0;
+
     if (device->convert.needed) {
         if (device->convert.src_format == AUDIO_U8) {
             silence = 0x80;
         } else {
             silence = 0;
         }
-        stream_len = device->convert.len;
+
+        /* If the result of the conversion alters the length, i.e. resampling is being used, use the streamer */
+        if (device->convert.len_mult != 1 || device->convert.len_div != 1) {
+            /* The streamer's maximum length should be twice whichever is larger: spec.size or len_cvt */
+            stream_max_len = 2 * device->spec.size;
+            if (device->convert.len_mult > device->convert.len_div) {
+                stream_max_len *= device->convert.len_mult;
+                stream_max_len /= device->convert.len_div;
+            }
+            if (SDL_StreamInit(&device->streamer, stream_max_len, silence) <
+                0)
+                return -1;
+            device->use_streamer = 1;
+
+            /* istream_len should be the length of what we grab from the callback and feed to conversion,
+               so that we get close to spec_size. I.e. we want device.spec_size = istream_len * u / d
+             */
+            istream_len =
+                device->spec.size * device->convert.len_div /
+                device->convert.len_mult;
+        }
+
+        /* stream_len = device->convert.len; */
+        stream_len = device->spec.size;
     } else {
         silence = device->spec.silence;
         stream_len = device->spec.size;
     }
 
-    /* Loop, filling the audio buffers */
-    while (device->enabled) {
+    /* Determine if the streamer is necessary here */
+    if (device->use_streamer == 1) {
+        /* This code is almost the same as the old code. The difference is, instead of reding
+           directly from the callback into "stream", then converting and sending the audio off,
+           we go: callback -> "istream" -> (conversion) -> streamer -> stream -> device.
+           However, reading and writing with streamer are done separately:
+           - We only call the callback and write to the streamer when the streamer does not
+           contain enough samples to output to the device.
+           - We only read from the streamer and tell the device to play when the streamer
+           does have enough samples to output.
+           This allows us to perform resampling in the conversion step, where the output of the
+           resampling process can be any number. We will have to see what a good size for the
+           stream's maximum length is, but I suspect 2*max(len_cvt, stream_len) is a good figure.
+         */
+        while (device->enabled) {
+            /* Only read in audio if the streamer doesn't have enough already (if it does not have enough samples to output) */
+            if (SDL_StreamLength(&device->streamer) < stream_len) {
+                /* Set up istream */
+                if (device->convert.needed) {
+                    if (device->convert.buf) {
+                        istream = device->convert.buf;
+                    } else {
+                        continue;
+                    }
+                } else {
+                    istream = current_audio.impl.GetDeviceBuf(device);
+                    if (istream == NULL) {
+                        istream = device->fake_stream;
+                    }
+                }
 
-        /* Fill the current buffer with sound */
-        if (device->convert.needed) {
-            if (device->convert.buf) {
-                stream = device->convert.buf;
-            } else {
-                continue;
+                /* Read from the callback into the _input_ stream */
+                if (!device->paused) {
+                    SDL_mutexP(device->mixer_lock);
+                    (*fill) (udata, istream, istream_len);
+                    SDL_mutexV(device->mixer_lock);
+                }
+
+                /* Convert the audio if necessary and write to the streamer */
+                if (device->convert.needed) {
+                    SDL_ConvertAudio(&device->convert);
+                    if (istream == NULL) {
+                        istream = device->fake_stream;
+                    }
+                    /*SDL_memcpy(istream, device->convert.buf, device->convert.len_cvt); */
+                    SDL_StreamWrite(&device->streamer, device->convert.buf,
+                                    device->convert.len_cvt);
+                } else {
+                    SDL_StreamWrite(&device->streamer, istream, istream_len);
+                }
             }
-        } else {
-            stream = current_audio.impl.GetDeviceBuf(device);
-            if (stream == NULL) {
-                stream = device->fake_stream;
+
+            /* Only output audio if the streamer has enough to output */
+            if (SDL_StreamLength(&device->streamer) >= stream_len) {
+                /* Set up the output stream */
+                if (device->convert.needed) {
+                    if (device->convert.buf) {
+                        stream = device->convert.buf;
+                    } else {
+                        continue;
+                    }
+                } else {
+                    stream = current_audio.impl.GetDeviceBuf(device);
+                    if (stream == NULL) {
+                        stream = device->fake_stream;
+                    }
+                }
+
+                /* Now read from the streamer */
+                SDL_StreamRead(&device->streamer, stream, stream_len);
+
+                /* Ready current buffer for play and change current buffer */
+                if (stream != device->fake_stream) {
+                    current_audio.impl.PlayDevice(device);
+                }
+
+                /* Wait for an audio buffer to become available */
+                if (stream == device->fake_stream) {
+                    SDL_Delay((device->spec.samples * 1000) /
+                              device->spec.freq);
+                } else {
+                    current_audio.impl.WaitDevice(device);
+                }
             }
-        }
 
-        if (!device->paused) {
-            SDL_mutexP(device->mixer_lock);
-            (*fill) (udata, stream, stream_len);
-            SDL_mutexV(device->mixer_lock);
         }
+    } else {
+        /* Otherwise, do not use the streamer. This is the old code. */
 
-        /* Convert the audio if necessary */
-        if (device->convert.needed) {
-            SDL_ConvertAudio(&device->convert);
-            stream = current_audio.impl.GetDeviceBuf(device);
-            if (stream == NULL) {
-                stream = device->fake_stream;
+        /* Loop, filling the audio buffers */
+        while (device->enabled) {
+
+            /* Fill the current buffer with sound */
+            if (device->convert.needed) {
+                if (device->convert.buf) {
+                    stream = device->convert.buf;
+                } else {
+                    continue;
+                }
+            } else {
+                stream = current_audio.impl.GetDeviceBuf(device);
+                if (stream == NULL) {
+                    stream = device->fake_stream;
+                }
             }
-            SDL_memcpy(stream, device->convert.buf, device->convert.len_cvt);
-        }
 
-        /* Ready current buffer for play and change current buffer */
-        if (stream != device->fake_stream) {
-            current_audio.impl.PlayDevice(device);
-        }
+            if (!device->paused) {
+                SDL_mutexP(device->mixer_lock);
+                (*fill) (udata, stream, stream_len);
+                SDL_mutexV(device->mixer_lock);
+            }
 
-        /* Wait for an audio buffer to become available */
-        if (stream == device->fake_stream) {
-            SDL_Delay((device->spec.samples * 1000) / device->spec.freq);
-        } else {
-            current_audio.impl.WaitDevice(device);
+            /* Convert the audio if necessary */
+            if (device->convert.needed) {
+                SDL_ConvertAudio(&device->convert);
+                stream = current_audio.impl.GetDeviceBuf(device);
+                if (stream == NULL) {
+                    stream = device->fake_stream;
+                }
+                SDL_memcpy(stream, device->convert.buf,
+                           device->convert.len_cvt);
+            }
+
+            /* Ready current buffer for play and change current buffer */
+            if (stream != device->fake_stream) {
+                current_audio.impl.PlayDevice(device);
+            }
+
+            /* Wait for an audio buffer to become available */
+            if (stream == device->fake_stream) {
+                SDL_Delay((device->spec.samples * 1000) / device->spec.freq);
+            } else {
+                current_audio.impl.WaitDevice(device);
+            }
         }
     }
 
     /* Wait for the audio to drain.. */
     current_audio.impl.WaitDone(device);
 
+    /* If necessary, deinit the streamer */
+    if (device->use_streamer == 1)
+        SDL_StreamDeinit(&device->streamer);
+
     return (0);
 }