audio: reworked audio streams to have right-hand resampling padding available.
authorRyan C. Gordon <icculus@icculus.org>
Tue, 10 Oct 2017 16:12:56 -0400
changeset 11583c48ab2c208a2
parent 11582 cc0b1273a381
child 11584 6d17410edb75
audio: reworked audio streams to have right-hand resampling padding available.

Fixes Bugzilla #3851.
src/audio/SDL_audiocvt.c
     1.1 --- a/src/audio/SDL_audiocvt.c	Tue Oct 10 11:56:54 2017 -0400
     1.2 +++ b/src/audio/SDL_audiocvt.c	Tue Oct 10 16:12:56 2017 -0400
     1.3 @@ -31,6 +31,8 @@
     1.4  #include "../SDL_dataqueue.h"
     1.5  #include "SDL_cpuinfo.h"
     1.6  
     1.7 +#define DEBUG_AUDIOSTREAM 0
     1.8 +
     1.9  #ifdef __SSE3__
    1.10  #define HAVE_SSE3_INTRINSICS 1
    1.11  #endif
    1.12 @@ -467,14 +469,20 @@
    1.13  static int
    1.14  ResamplerPadding(const int inrate, const int outrate)
    1.15  {
    1.16 -    return (inrate > outrate) ? (int) SDL_ceil(((float) (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * inrate) / ((float) outrate))) : RESAMPLER_SAMPLES_PER_ZERO_CROSSING;
    1.17 +    if (inrate == outrate) {
    1.18 +        return 0;
    1.19 +    } else if (inrate > outrate) {
    1.20 +        return (int) SDL_ceil(((float) (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * inrate) / ((float) outrate)));
    1.21 +    }
    1.22 +    return RESAMPLER_SAMPLES_PER_ZERO_CROSSING;
    1.23  }
    1.24  
    1.25  /* lpadding and rpadding are expected to be buffers of (ResamplePadding(inrate, outrate) * chans * sizeof (float)) bytes. */
    1.26  static int
    1.27  SDL_ResampleAudio(const int chans, const int inrate, const int outrate,
    1.28 -                        float *lpadding, float *rpadding, const float *inbuf,
    1.29 -                        const int inbuflen, float *outbuf, const int outbuflen)
    1.30 +                        const float *lpadding, const float *rpadding,
    1.31 +                        const float *inbuf, const int inbuflen,
    1.32 +                        float *outbuf, const int outbuflen)
    1.33  {
    1.34      const float outtimeincr = 1.0f / ((float) outrate);
    1.35      const float ratio = ((float) outrate) / ((float) inrate);
    1.36 @@ -483,7 +491,7 @@
    1.37      const int inframes = inbuflen / framelen;
    1.38      const int wantedoutframes = (int) ((inbuflen / framelen) * ratio);  /* outbuflen isn't total to write, it's total available. */
    1.39      const int maxoutframes = outbuflen / framelen;
    1.40 -    const int outframes = (wantedoutframes < maxoutframes) ? wantedoutframes : maxoutframes;
    1.41 +    const int outframes = SDL_min(wantedoutframes, maxoutframes);
    1.42      float *dst = outbuf;
    1.43      float outtime = 0.0f;
    1.44      int i, j, chan;
    1.45 @@ -1076,6 +1084,7 @@
    1.46      SDL_AudioCVT cvt_before_resampling;
    1.47      SDL_AudioCVT cvt_after_resampling;
    1.48      SDL_DataQueue *queue;
    1.49 +    SDL_bool first_run;
    1.50      Uint8 *work_buffer_base;  /* maybe unaligned pointer from SDL_realloc(). */
    1.51      int work_buffer_len;
    1.52      int src_sample_frame_size;
    1.53 @@ -1089,6 +1098,8 @@
    1.54      double rate_incr;
    1.55      Uint8 pre_resample_channels;
    1.56      int packetlen;
    1.57 +    int resampler_padding_samples;
    1.58 +    float *resampler_padding;
    1.59      void *resampler_state;
    1.60      SDL_ResampleAudioStreamFunc resampler_func;
    1.61      SDL_ResetAudioStreamResamplerFunc reset_resampler_func;
    1.62 @@ -1129,16 +1140,7 @@
    1.63      SRC_DATA data;
    1.64      int result;
    1.65  
    1.66 -    if (inbuf == ((const float *) outbuf)) {  /* libsamplerate can't work in-place. */
    1.67 -        Uint8 *ptr = EnsureStreamBufferSize(stream, inbuflen + outbuflen);
    1.68 -        if (ptr == NULL) {
    1.69 -            SDL_OutOfMemory();
    1.70 -            return 0;
    1.71 -        }
    1.72 -        SDL_memcpy(ptr + outbuflen, ptr, inbuflen);
    1.73 -        inbuf = (const float *) (ptr + outbuflen);
    1.74 -        outbuf = (float *) ptr;
    1.75 -    }
    1.76 +    SDL_assert(inbuf != ((const float *) outbuf));  /* SDL_AudioStreamPut() shouldn't allow in-place resamples. */
    1.77  
    1.78      data.data_in = (float *)inbuf; /* Older versions of libsamplerate had a non-const pointer, but didn't write to it */
    1.79      data.input_frames = inbuflen / framelen;
    1.80 @@ -1213,54 +1215,32 @@
    1.81  static int
    1.82  SDL_ResampleAudioStream(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
    1.83  {
    1.84 +    const Uint8 *inbufend = ((const Uint8 *) _inbuf) + inbuflen;
    1.85      const float *inbuf = (const float *) _inbuf;
    1.86      float *outbuf = (float *) _outbuf;
    1.87      const int chans = (int) stream->pre_resample_channels;
    1.88      const int inrate = stream->src_rate;
    1.89      const int outrate = stream->dst_rate;
    1.90 -    const int paddingsamples = ResamplerPadding(inrate, outrate) * chans;
    1.91 +    const int paddingsamples = stream->resampler_padding_samples;
    1.92      const int paddingbytes = paddingsamples * sizeof (float);
    1.93      float *lpadding = (float *) stream->resampler_state;
    1.94 -    float *rpadding;
    1.95 +    const float *rpadding = (const float *) inbufend; /* we set this up so there are valid padding samples at the end of the input buffer. */
    1.96      int retval;
    1.97  
    1.98 -    if (inbuf == ((const float *) outbuf)) {  /* !!! FIXME can't work in-place (for now!). */
    1.99 -        Uint8 *ptr = EnsureStreamBufferSize(stream, inbuflen + outbuflen);
   1.100 -        if (ptr == NULL) {
   1.101 -            SDL_OutOfMemory();
   1.102 -            return 0;
   1.103 -        }
   1.104 -        SDL_memcpy(ptr + outbuflen, ptr, inbuflen);
   1.105 -        inbuf = (const float *) (ptr + outbuflen);
   1.106 -        outbuf = (float *) ptr;
   1.107 -    }
   1.108 -
   1.109 -    /* !!! FIXME: streaming current resamples on Put, because of probably good reasons I can't remember right now, but if we resample on Get, we'd be able to access legit right padding values. */
   1.110 -    rpadding = SDL_stack_alloc(float, paddingsamples);
   1.111 -    if (!rpadding) {
   1.112 -        SDL_OutOfMemory();
   1.113 -        return 0;
   1.114 -    }
   1.115 -    SDL_memset(rpadding, '\0', paddingbytes);
   1.116 +    SDL_assert(inbuf != ((const float *) outbuf));  /* SDL_AudioStreamPut() shouldn't allow in-place resamples. */
   1.117  
   1.118      retval = SDL_ResampleAudio(chans, inrate, outrate, lpadding, rpadding, inbuf, inbuflen, outbuf, outbuflen);
   1.119  
   1.120 -    SDL_stack_free(rpadding);
   1.121 -
   1.122      /* update our left padding with end of current input, for next run. */
   1.123 -    SDL_memcpy(lpadding, ((const Uint8 *) inbuf) + (inbuflen - paddingbytes), paddingbytes);
   1.124 -
   1.125 +    SDL_memcpy(lpadding, inbufend - paddingbytes, paddingbytes);
   1.126      return retval;
   1.127  }
   1.128  
   1.129  static void
   1.130  SDL_ResetAudioStreamResampler(SDL_AudioStream *stream)
   1.131  {
   1.132 -    /* set all the left padding to silence. */
   1.133 -    const int inrate = stream->src_rate;
   1.134 -    const int outrate = stream->dst_rate;
   1.135 -    const int chans = (int) stream->pre_resample_channels;
   1.136 -    const int len = ResamplerPadding(inrate, outrate) * chans;
   1.137 +    /* set all the padding to silence. */
   1.138 +    const int len = stream->resampler_padding_samples;
   1.139      SDL_memset(stream->resampler_state, '\0', len * sizeof (float));
   1.140  }
   1.141  
   1.142 @@ -1293,6 +1273,7 @@
   1.143         the resampled data (!!! FIXME: decide if that works in practice, though!). */
   1.144      pre_resample_channels = SDL_min(src_channels, dst_channels);
   1.145  
   1.146 +    retval->first_run = SDL_TRUE;
   1.147      retval->src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels;
   1.148      retval->src_format = src_format;
   1.149      retval->src_channels = src_channels;
   1.150 @@ -1304,6 +1285,14 @@
   1.151      retval->pre_resample_channels = pre_resample_channels;
   1.152      retval->packetlen = packetlen;
   1.153      retval->rate_incr = ((double) dst_rate) / ((double) src_rate);
   1.154 +    retval->resampler_padding_samples = ResamplerPadding(retval->src_rate, retval->dst_rate) * pre_resample_channels;
   1.155 +    retval->resampler_padding = (float *) SDL_calloc(retval->resampler_padding_samples, sizeof (float));
   1.156 +
   1.157 +    if (retval->resampler_padding == NULL) {
   1.158 +        SDL_FreeAudioStream(retval);
   1.159 +        SDL_OutOfMemory();
   1.160 +        return NULL;
   1.161 +    }
   1.162  
   1.163      /* Not resampling? It's an easy conversion (and maybe not even that!). */
   1.164      if (src_rate == dst_rate) {
   1.165 @@ -1325,9 +1314,7 @@
   1.166  #endif
   1.167  
   1.168          if (!retval->resampler_func) {
   1.169 -            const int chans = (int) pre_resample_channels;
   1.170 -            const int len = ResamplerPadding(src_rate, dst_rate) * chans;
   1.171 -            retval->resampler_state = SDL_calloc(len, sizeof (float));
   1.172 +            retval->resampler_state = SDL_calloc(retval->resampler_padding_samples, sizeof (float));
   1.173              if (!retval->resampler_state) {
   1.174                  SDL_FreeAudioStream(retval);
   1.175                  SDL_OutOfMemory();
   1.176 @@ -1366,7 +1353,12 @@
   1.177  SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _buflen)
   1.178  {
   1.179      int buflen = (int) _buflen;
   1.180 -    const void *origbuf = buf;
   1.181 +    int workbuflen;
   1.182 +    Uint8 *workbuf;
   1.183 +    Uint8 *resamplebuf = NULL;
   1.184 +    int resamplebuflen = 0;
   1.185 +    const int neededpaddingbytes = stream ? stream->resampler_padding_samples * sizeof (float) : 0;
   1.186 +    int paddingbytes;
   1.187  
   1.188      /* !!! FIXME: several converters can take advantage of SIMD, but only
   1.189         !!! FIXME:  if the data is aligned to 16 bytes. EnsureStreamBufferSize()
   1.190 @@ -1376,6 +1368,10 @@
   1.191         !!! FIXME:  isn't a multiple of 16. In these cases, we should chop off
   1.192         !!! FIXME:  a few samples at the end and convert them separately. */
   1.193  
   1.194 +    #if DEBUG_AUDIOSTREAM
   1.195 +    printf("AUDIOSTREAM: wants to put %d preconverted bytes\n", buflen);
   1.196 +    #endif
   1.197 +
   1.198      if (!stream) {
   1.199          return SDL_InvalidParamError("stream");
   1.200      } else if (!buf) {
   1.201 @@ -1384,60 +1380,114 @@
   1.202          return 0;  /* nothing to do. */
   1.203      } else if ((buflen % stream->src_sample_frame_size) != 0) {
   1.204          return SDL_SetError("Can't add partial sample frames");
   1.205 +    } else if (buflen < (neededpaddingbytes * 2)) {
   1.206 +        return SDL_SetError("Need to put a larger buffer");
   1.207      }
   1.208  
   1.209 +    /* no padding prepended on first run. */
   1.210 +    paddingbytes = stream->first_run ? 0 : neededpaddingbytes;
   1.211 +    stream->first_run = SDL_FALSE;
   1.212 +
   1.213 +    if (!stream->cvt_before_resampling.needed &&
   1.214 +        (stream->dst_rate == stream->src_rate) &&
   1.215 +        !stream->cvt_after_resampling.needed) {
   1.216 +        #if DEBUG_AUDIOSTREAM
   1.217 +        printf("AUDIOSTREAM: no conversion needed at all, queueing %d bytes.\n", buflen);
   1.218 +        #endif
   1.219 +        return SDL_WriteToDataQueue(stream->queue, buf, buflen);
   1.220 +    }
   1.221 +
   1.222 +    /* Make sure the work buffer can hold all the data we need at once... */
   1.223 +    workbuflen = buflen;
   1.224      if (stream->cvt_before_resampling.needed) {
   1.225 -        const int workbuflen = buflen * stream->cvt_before_resampling.len_mult;  /* will be "* 1" if not needed */
   1.226 -        Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen);
   1.227 -        if (workbuf == NULL) {
   1.228 -            return -1;  /* probably out of memory. */
   1.229 -        }
   1.230 -        SDL_assert(buf == origbuf);
   1.231 -        SDL_memcpy(workbuf, buf, buflen);
   1.232 -        stream->cvt_before_resampling.buf = workbuf;
   1.233 +        workbuflen *= stream->cvt_before_resampling.len_mult;
   1.234 +    }
   1.235 +
   1.236 +    if (stream->dst_rate != stream->src_rate) {
   1.237 +        /* resamples can't happen in place, so make space for second buf. */
   1.238 +        const int framesize = stream->pre_resample_channels * sizeof (float);
   1.239 +        const int frames = workbuflen / framesize;
   1.240 +        resamplebuflen = ((int) SDL_ceil(frames * stream->rate_incr)) * framesize;
   1.241 +        #if DEBUG_AUDIOSTREAM
   1.242 +        printf("AUDIOSTREAM: will resample %d bytes to %d (ratio=%.6f)\n", workbuflen, resamplebuflen, stream->rate_incr);
   1.243 +        #endif
   1.244 +        workbuflen += resamplebuflen;
   1.245 +    }
   1.246 +
   1.247 +    if (stream->cvt_after_resampling.needed) {
   1.248 +        /* !!! FIXME: buffer might be big enough already? */
   1.249 +        workbuflen *= stream->cvt_after_resampling.len_mult;
   1.250 +    }
   1.251 +
   1.252 +    workbuflen += neededpaddingbytes;
   1.253 +
   1.254 +    #if DEBUG_AUDIOSTREAM
   1.255 +    printf("AUDIOSTREAM: Putting %d bytes of preconverted audio, need %d byte work buffer\n", buflen, workbuflen);
   1.256 +    #endif
   1.257 +
   1.258 +    workbuf = EnsureStreamBufferSize(stream, workbuflen);
   1.259 +    if (!workbuf) {
   1.260 +        return -1;  /* probably out of memory. */
   1.261 +    }
   1.262 +
   1.263 +    resamplebuf = workbuf;  /* default if not resampling. */
   1.264 +
   1.265 +    SDL_memcpy(workbuf + paddingbytes, buf, buflen);
   1.266 +
   1.267 +    if (stream->cvt_before_resampling.needed) {
   1.268 +        stream->cvt_before_resampling.buf = workbuf + paddingbytes;
   1.269          stream->cvt_before_resampling.len = buflen;
   1.270          if (SDL_ConvertAudio(&stream->cvt_before_resampling) == -1) {
   1.271              return -1;   /* uhoh! */
   1.272          }
   1.273 -        buf = workbuf;
   1.274          buflen = stream->cvt_before_resampling.len_cvt;
   1.275 +
   1.276 +        #if DEBUG_AUDIOSTREAM
   1.277 +        printf("AUDIOSTREAM: After initial conversion we have %d bytes\n", buflen);
   1.278 +        #endif
   1.279      }
   1.280  
   1.281      if (stream->dst_rate != stream->src_rate) {
   1.282 -        const int workbuflen = buflen * ((int) SDL_ceil(stream->rate_incr));
   1.283 -        Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen);
   1.284 -        if (workbuf == NULL) {
   1.285 -            return -1;  /* probably out of memory. */
   1.286 +        /* save off some samples at the end; they are used for padding now so
   1.287 +           the resampler is coherent and then used at the start of the next
   1.288 +           put operation. Prepend last put operation's padding, too. */
   1.289 +
   1.290 +        /* prepend prior put's padding. :P */
   1.291 +        if (paddingbytes) {
   1.292 +            SDL_memcpy(workbuf, stream->resampler_padding, paddingbytes);
   1.293 +            buflen += paddingbytes;
   1.294          }
   1.295 -        /* don't SDL_memcpy(workbuf, buf, buflen) here; our resampler can work inplace or not,
   1.296 -           libsamplerate needs buffers to be separate; either way, avoid a copy here if possible. */
   1.297 -        if (buf != origbuf) {
   1.298 -            buf = workbuf;  /* in case we realloc()'d the pointer. */
   1.299 -        }
   1.300 -        buflen = stream->resampler_func(stream, buf, buflen, workbuf, workbuflen);
   1.301 -        buf = EnsureStreamBufferSize(stream, workbuflen);
   1.302 -        SDL_assert(buf != NULL);  /* shouldn't be growing, just aligning. */
   1.303 +
   1.304 +        /* save off the data at the end for the next run. */
   1.305 +        SDL_memcpy(stream->resampler_padding, workbuf + (buflen - neededpaddingbytes), neededpaddingbytes);
   1.306 +
   1.307 +        resamplebuf = workbuf + buflen;  /* skip to second piece of workbuf. */
   1.308 +        buflen = stream->resampler_func(stream, workbuf, buflen - neededpaddingbytes, resamplebuf, resamplebuflen);
   1.309 +
   1.310 +        #if DEBUG_AUDIOSTREAM
   1.311 +        printf("AUDIOSTREAM: After resampling we have %d bytes\n", buflen);
   1.312 +        #endif
   1.313      }
   1.314  
   1.315      if (stream->cvt_after_resampling.needed) {
   1.316 -        const int workbuflen = buflen * stream->cvt_after_resampling.len_mult;  /* will be "* 1" if not needed */
   1.317 -        Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen);
   1.318 -        if (workbuf == NULL) {
   1.319 -            return -1;  /* probably out of memory. */
   1.320 -        }
   1.321 -        if (buf == origbuf) {  /* copy if we haven't before. */
   1.322 -            SDL_memcpy(workbuf, origbuf, buflen);
   1.323 -        }
   1.324 -        stream->cvt_after_resampling.buf = workbuf;
   1.325 +        stream->cvt_after_resampling.buf = resamplebuf;
   1.326          stream->cvt_after_resampling.len = buflen;
   1.327          if (SDL_ConvertAudio(&stream->cvt_after_resampling) == -1) {
   1.328              return -1;   /* uhoh! */
   1.329          }
   1.330 -        buf = workbuf;
   1.331          buflen = stream->cvt_after_resampling.len_cvt;
   1.332 +
   1.333 +        #if DEBUG_AUDIOSTREAM
   1.334 +        printf("AUDIOSTREAM: After final conversion we have %d bytes\n", buflen);
   1.335 +        #endif
   1.336      }
   1.337  
   1.338 -    return SDL_WriteToDataQueue(stream->queue, buf, buflen);
   1.339 +    #if DEBUG_AUDIOSTREAM
   1.340 +    printf("AUDIOSTREAM: Final output is %d bytes\n", buflen);
   1.341 +    #endif
   1.342 +
   1.343 +    /* resamplebuf holds the final output, even if we didn't resample. */
   1.344 +    return SDL_WriteToDataQueue(stream->queue, resamplebuf, buflen);
   1.345  }
   1.346  
   1.347  void
   1.348 @@ -1450,6 +1500,7 @@
   1.349          if (stream->reset_resampler_func) {
   1.350              stream->reset_resampler_func(stream);
   1.351          }
   1.352 +        stream->first_run = SDL_TRUE;
   1.353      }
   1.354  }
   1.355  
   1.356 @@ -1458,6 +1509,10 @@
   1.357  int
   1.358  SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, const Uint32 len)
   1.359  {
   1.360 +    #if DEBUG_AUDIOSTREAM
   1.361 +    printf("AUDIOSTREAM: want to get %u converted bytes\n", (unsigned int) len);
   1.362 +    #endif
   1.363 +
   1.364      if (!stream) {
   1.365          return SDL_InvalidParamError("stream");
   1.366      } else if (!buf) {
   1.367 @@ -1488,6 +1543,7 @@
   1.368          }
   1.369          SDL_FreeDataQueue(stream->queue);
   1.370          SDL_free(stream->work_buffer_base);
   1.371 +        SDL_free(stream->resampler_padding);
   1.372          SDL_free(stream);
   1.373      }
   1.374  }