audio: Stream resampling now saves some samples from previous run for padding.
authorRyan C. Gordon <icculus@icculus.org>
Fri, 22 Sep 2017 07:42:24 -0400
changeset 11517beb96c015b30
parent 11516 68a767ae3a88
child 11518 0c51e6ef1b73
audio: Stream resampling now saves some samples from previous run for padding.

Previously, the padding was silence, which was a problem when streaming since
you would sample a little bit of this silence between each buffer.

We still need a means to get padding data for the right hand side, but this
patch makes the resampler output more correct.
src/audio/SDL_audiocvt.c
     1.1 --- a/src/audio/SDL_audiocvt.c	Thu Sep 21 18:38:07 2017 -0700
     1.2 +++ b/src/audio/SDL_audiocvt.c	Fri Sep 22 07:42:24 2017 -0400
     1.3 @@ -464,15 +464,21 @@
     1.4      ResamplerFilterDifference = NULL;
     1.5  }
     1.6  
     1.7 +static int
     1.8 +ResamplerPadding(const int inrate, const int outrate)
     1.9 +{
    1.10 +    return (inrate > outrate) ? (int) SDL_ceil(((float) (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * inrate) / ((float) outrate))) : RESAMPLER_SAMPLES_PER_ZERO_CROSSING;
    1.11 +}
    1.12  
    1.13 +/* lpadding and rpadding are expected to be buffers of (ResamplePadding(inrate, outrate) * chans * sizeof (float)) bytes. */
    1.14  static int
    1.15  SDL_ResampleAudio(const int chans, const int inrate, const int outrate,
    1.16 -                        float *last_sample, const float *inbuf,
    1.17 +                        float *lpadding, float *rpadding, const float *inbuf,
    1.18                          const int inbuflen, float *outbuf, const int outbuflen)
    1.19  {
    1.20      const float outtimeincr = 1.0f / ((float) outrate);
    1.21      const float ratio = ((float) outrate) / ((float) inrate);
    1.22 -    /*const int padding_len = (ratio < 1.0f) ? (int) SDL_ceilf(((float) (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * inrate) / ((float) outrate))) : RESAMPLER_SAMPLES_PER_ZERO_CROSSING;*/
    1.23 +    const int paddinglen = ResamplerPadding(inrate, outrate);
    1.24      const int framelen = chans * (int)sizeof (float);
    1.25      const int inframes = inbuflen / framelen;
    1.26      const int wantedoutframes = (int) ((inbuflen / framelen) * ratio);  /* outbuflen isn't total to write, it's total available. */
    1.27 @@ -499,16 +505,16 @@
    1.28              /* do this twice to calculate the sample, once for the "left wing" and then same for the right. */
    1.29              /* !!! FIXME: do both wings in one loop */
    1.30              for (j = 0; (filterindex1 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) {
    1.31 -                /* !!! FIXME: insample uses zero for padding samples, but it should use prior state from last_sample. */
    1.32                  const int srcframe = srcindex - j;
    1.33 -                const float insample = (srcframe < 0) ? 0.0f : inbuf[(srcframe * chans) + chan];  /* !!! FIXME: we can bubble this conditional out of here by doing a pre loop. */
    1.34 +                /* !!! FIXME: we can bubble this conditional out of here by doing a pre loop. */
    1.35 +                const float insample = (srcframe < 0) ? lpadding[((paddinglen + srcframe) * chans) + chan] : inbuf[(srcframe * chans) + chan];
    1.36                  outsample += (insample * (ResamplerFilter[filterindex1 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)] + (interpolation1 * ResamplerFilterDifference[filterindex1 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)])));
    1.37              }
    1.38  
    1.39              for (j = 0; (filterindex2 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) {
    1.40                  const int srcframe = srcindex + 1 + j;
    1.41 -                /* !!! FIXME: insample uses zero for padding samples, but it should use prior state from last_sample. */
    1.42 -                const float insample = (srcframe >= inframes) ? 0.0f : inbuf[(srcframe * chans) + chan];  /* !!! FIXME: we can bubble this conditional out of here by doing a post loop. */
    1.43 +                /* !!! FIXME: we can bubble this conditional out of here by doing a post loop. */
    1.44 +                const float insample = (srcframe >= inframes) ? rpadding[((srcframe - inframes) * chans) + chan] : inbuf[(srcframe * chans) + chan];
    1.45                  outsample += (insample * (ResamplerFilter[filterindex2 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)] + (interpolation2 * ResamplerFilterDifference[filterindex2 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)])));
    1.46              }
    1.47              *(dst++) = outsample;
    1.48 @@ -693,8 +699,8 @@
    1.49      /* !!! FIXME in 2.1: there are ten slots in the filter list, and the theoretical maximum we use is six (seven with NULL terminator).
    1.50         !!! FIXME in 2.1:   We need to store data for this resampler, because the cvt structure doesn't store the original sample rates,
    1.51         !!! FIXME in 2.1:   so we steal the ninth and tenth slot.  :( */
    1.52 -    const int srcrate = (int) (size_t) cvt->filters[SDL_AUDIOCVT_MAX_FILTERS-1];
    1.53 -    const int dstrate = (int) (size_t) cvt->filters[SDL_AUDIOCVT_MAX_FILTERS];
    1.54 +    const int inrate = (int) (size_t) cvt->filters[SDL_AUDIOCVT_MAX_FILTERS-1];
    1.55 +    const int outrate = (int) (size_t) cvt->filters[SDL_AUDIOCVT_MAX_FILTERS];
    1.56      const float *src = (const float *) cvt->buf;
    1.57      const int srclen = cvt->len_cvt;
    1.58      /*float *dst = (float *) cvt->buf;
    1.59 @@ -702,13 +708,15 @@
    1.60      /* !!! FIXME: remove this if we can get the resampler to work in-place again. */
    1.61      float *dst = (float *) (cvt->buf + srclen);
    1.62      const int dstlen = (cvt->len * cvt->len_mult) - srclen;
    1.63 -    float state[8];
    1.64 +    const int paddingsamples = (ResamplerPadding(inrate, outrate) * chans);
    1.65 +    float *padding = SDL_stack_alloc(float, paddingsamples);
    1.66  
    1.67      SDL_assert(format == AUDIO_F32SYS);
    1.68  
    1.69 -    SDL_zero(state);
    1.70 +    /* we keep no streaming state here, so pad with silence on both ends. */
    1.71 +    SDL_memset(padding, '\0', paddingsamples * sizeof (float));
    1.72  
    1.73 -    cvt->len_cvt = SDL_ResampleAudio(chans, srcrate, dstrate, state, src, srclen, dst, dstlen);
    1.74 +    cvt->len_cvt = SDL_ResampleAudio(chans, inrate, outrate, padding, padding, src, srclen, dst, dstlen);
    1.75  
    1.76      SDL_memcpy(cvt->buf, dst, cvt->len_cvt);  /* !!! FIXME: remove this if we can get the resampler to work in-place again. */
    1.77  
    1.78 @@ -1195,25 +1203,19 @@
    1.79  #endif /* HAVE_LIBSAMPLERATE_H */
    1.80  
    1.81  
    1.82 -typedef struct
    1.83 -{
    1.84 -    SDL_bool resampler_seeded;
    1.85 -    union
    1.86 -    {
    1.87 -        float f[8];
    1.88 -        Sint16 si16[2];
    1.89 -    } resampler_state;
    1.90 -} SDL_AudioStreamResamplerState;
    1.91 -
    1.92  static int
    1.93  SDL_ResampleAudioStream(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
    1.94  {
    1.95      const float *inbuf = (const float *) _inbuf;
    1.96      float *outbuf = (float *) _outbuf;
    1.97 -    SDL_AudioStreamResamplerState *state = (SDL_AudioStreamResamplerState*)stream->resampler_state;
    1.98 -    const int chans = (int)stream->pre_resample_channels;
    1.99 -
   1.100 -    SDL_assert(chans <= SDL_arraysize(state->resampler_state.f));
   1.101 +    const int chans = (int) stream->pre_resample_channels;
   1.102 +    const int inrate = stream->src_rate;
   1.103 +    const int outrate = stream->dst_rate;
   1.104 +    const int paddingsamples = ResamplerPadding(inrate, outrate) * chans;
   1.105 +    const int paddingbytes = paddingsamples * sizeof (float);
   1.106 +    float *lpadding = (float *) stream->resampler_state;
   1.107 +    float *rpadding = SDL_stack_alloc(float, paddingsamples);
   1.108 +    int retval;
   1.109  
   1.110      if (inbuf == ((const float *) outbuf)) {  /* !!! FIXME can't work in-place (for now!). */
   1.111          Uint8 *ptr = EnsureStreamBufferSize(stream, inbuflen + outbuflen);
   1.112 @@ -1226,19 +1228,25 @@
   1.113          outbuf = (float *) ptr;
   1.114      }
   1.115  
   1.116 -    if (!state->resampler_seeded) {
   1.117 -        SDL_zero(state->resampler_state.f);
   1.118 -        state->resampler_seeded = SDL_TRUE;
   1.119 -    }
   1.120 +    /* !!! 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.121 +    SDL_memset(rpadding, '\0', paddingbytes);
   1.122 +    retval = SDL_ResampleAudio(chans, inrate, outrate, lpadding, rpadding, inbuf, inbuflen, outbuf, outbuflen);
   1.123  
   1.124 -    return SDL_ResampleAudio(chans, stream->src_rate, stream->dst_rate, state->resampler_state.f, inbuf, inbuflen, outbuf, outbuflen);
   1.125 +    /* update our left padding with end of current input, for next run. */
   1.126 +    SDL_memcpy(lpadding, ((const Uint8 *) inbuf) + (inbuflen - paddingbytes), paddingbytes);
   1.127 +
   1.128 +    return retval;
   1.129  }
   1.130  
   1.131  static void
   1.132  SDL_ResetAudioStreamResampler(SDL_AudioStream *stream)
   1.133  {
   1.134 -    SDL_AudioStreamResamplerState *state = (SDL_AudioStreamResamplerState*)stream->resampler_state;
   1.135 -    state->resampler_seeded = SDL_FALSE;
   1.136 +    /* set all the left padding to silence. */
   1.137 +    const int inrate = stream->src_rate;
   1.138 +    const int outrate = stream->dst_rate;
   1.139 +    const int chans = (int) stream->pre_resample_channels;
   1.140 +    const int len = ResamplerPadding(inrate, outrate) * chans;
   1.141 +    SDL_memset(stream->resampler_state, '\0', len * sizeof (float));
   1.142  }
   1.143  
   1.144  static void
   1.145 @@ -1302,7 +1310,9 @@
   1.146  #endif
   1.147  
   1.148          if (!retval->resampler_func) {
   1.149 -            retval->resampler_state = SDL_calloc(1, sizeof(SDL_AudioStreamResamplerState));
   1.150 +            const int chans = (int) pre_resample_channels;
   1.151 +            const int len = ResamplerPadding(src_rate, dst_rate) * chans;
   1.152 +            retval->resampler_state = SDL_calloc(len, sizeof (float));
   1.153              if (!retval->resampler_state) {
   1.154                  SDL_FreeAudioStream(retval);
   1.155                  SDL_OutOfMemory();