src/audio/SDL_audiocvt.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 12 Jun 2017 16:39:15 -0700
changeset 11096 819384789a7a
parent 10926 97c829825e0e
child 11097 62a0a6e9b48b
permissions -rw-r--r--
Fixed bug 3668 - Overflow of SDL_AudioCVT.filters with some downmixes

Simon Hug

There's a chance that an audio conversion from many channels to a few can use more than 9 audio filters. SDL_AudioCVT has 10 SDL_AudioFilter pointers of which one has to be the terminating NULL pointer. The SDL code has no checks for this limit. If it overflows there can be stack or heap corruption or a call to 0xa.

Attached patch adds a function that checks for this limit and throws an error if it is reached. Also adds some documentation.

Test parameters that trigger this issue:
AUDIO_U16MSB with 224 channels at 46359 Hz
V
AUDIO_S16MSB with 6 channels at 27463 Hz

The fuzzer program I uploaded in bug 3667 has more of them.
slouken@0
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@10737
     3
  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
slouken@0
     4
slouken@5535
     5
  This software is provided 'as-is', without any express or implied
slouken@5535
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@5535
     7
  arising from the use of this software.
slouken@0
     8
slouken@5535
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@5535
    10
  including commercial applications, and to alter it and redistribute it
slouken@5535
    11
  freely, subject to the following restrictions:
slouken@0
    12
slouken@5535
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@5535
    14
     claim that you wrote the original software. If you use this software
slouken@5535
    15
     in a product, an acknowledgment in the product documentation would be
slouken@5535
    16
     appreciated but is not required.
slouken@5535
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@5535
    18
     misrepresented as being the original software.
slouken@5535
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@0
    20
*/
icculus@8093
    21
#include "../SDL_internal.h"
slouken@2728
    22
slouken@0
    23
/* Functions for audio drivers to perform runtime conversion of audio format */
slouken@0
    24
slouken@0
    25
#include "SDL_audio.h"
icculus@1982
    26
#include "SDL_audio_c.h"
slouken@0
    27
slouken@10773
    28
#include "SDL_loadso.h"
icculus@6281
    29
#include "SDL_assert.h"
icculus@10757
    30
#include "../SDL_dataqueue.h"
icculus@10835
    31
#include "SDL_cpuinfo.h"
icculus@6281
    32
icculus@10835
    33
#ifdef __SSE3__
icculus@10835
    34
#define HAVE_SSE3_INTRINSICS 1
icculus@10832
    35
#endif
icculus@10832
    36
icculus@10832
    37
#if HAVE_SSE3_INTRINSICS
icculus@10832
    38
/* Effectively mix right and left channels into a single channel */
icculus@10832
    39
static void SDLCALL
icculus@10832
    40
SDL_ConvertStereoToMono_SSE3(SDL_AudioCVT * cvt, SDL_AudioFormat format)
icculus@10832
    41
{
icculus@10832
    42
    float *dst = (float *) cvt->buf;
icculus@10832
    43
    const float *src = dst;
icculus@10832
    44
    int i = cvt->len_cvt / 8;
icculus@10832
    45
icculus@10832
    46
    LOG_DEBUG_CONVERT("stereo", "mono (using SSE3)");
icculus@10832
    47
    SDL_assert(format == AUDIO_F32SYS);
icculus@10832
    48
icculus@10832
    49
    /* We can only do this if dst is aligned to 16 bytes; since src is the
icculus@10832
    50
       same pointer and it moves by 2, it can't be forcibly aligned. */
icculus@10832
    51
    if ((((size_t) dst) & 15) == 0) {
icculus@10832
    52
        /* Aligned! Do SSE blocks as long as we have 16 bytes available. */
icculus@10832
    53
        const __m128 divby2 = _mm_set1_ps(0.5f);
icculus@10832
    54
        while (i >= 4) {   /* 4 * float32 */
icculus@10832
    55
            _mm_store_ps(dst, _mm_mul_ps(_mm_hadd_ps(_mm_load_ps(src), _mm_load_ps(src+4)), divby2));
icculus@10832
    56
            i -= 4; src += 8; dst += 4;
icculus@10832
    57
        }
icculus@10832
    58
    }
icculus@10832
    59
icculus@10832
    60
    /* Finish off any leftovers with scalar operations. */
icculus@10832
    61
    while (i) {
icculus@10832
    62
        *dst = (src[0] + src[1]) * 0.5f;
icculus@10832
    63
        dst++; i--; src += 2;
icculus@10832
    64
    }
icculus@10832
    65
icculus@10832
    66
    cvt->len_cvt /= 2;
icculus@10832
    67
    if (cvt->filters[++cvt->filter_index]) {
icculus@10832
    68
        cvt->filters[cvt->filter_index] (cvt, format);
icculus@10832
    69
    }
icculus@10832
    70
}
icculus@10832
    71
#endif
icculus@10832
    72
slouken@0
    73
/* Effectively mix right and left channels into a single channel */
icculus@1982
    74
static void SDLCALL
icculus@10793
    75
SDL_ConvertStereoToMono(SDL_AudioCVT * cvt, SDL_AudioFormat format)
slouken@0
    76
{
icculus@10575
    77
    float *dst = (float *) cvt->buf;
icculus@10575
    78
    const float *src = dst;
slouken@1895
    79
    int i;
slouken@0
    80
icculus@10575
    81
    LOG_DEBUG_CONVERT("stereo", "mono");
icculus@10575
    82
    SDL_assert(format == AUDIO_F32SYS);
slouken@0
    83
icculus@10575
    84
    for (i = cvt->len_cvt / 8; i; --i, src += 2) {
icculus@10831
    85
        *(dst++) = (src[0] + src[1]) * 0.5f;
slouken@1895
    86
    }
icculus@1982
    87
slouken@1895
    88
    cvt->len_cvt /= 2;
slouken@1895
    89
    if (cvt->filters[++cvt->filter_index]) {
slouken@1895
    90
        cvt->filters[cvt->filter_index] (cvt, format);
slouken@1895
    91
    }
slouken@0
    92
}
slouken@0
    93
icculus@1982
    94
icculus@10793
    95
/* Convert from 5.1 to stereo. Average left and right, discard subwoofer. */
icculus@1982
    96
static void SDLCALL
icculus@10793
    97
SDL_Convert51ToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
slouken@942
    98
{
icculus@10575
    99
    float *dst = (float *) cvt->buf;
icculus@10575
   100
    const float *src = dst;
slouken@1895
   101
    int i;
slouken@942
   102
icculus@10793
   103
    LOG_DEBUG_CONVERT("5.1", "stereo");
icculus@10575
   104
    SDL_assert(format == AUDIO_F32SYS);
slouken@942
   105
icculus@10793
   106
    /* this assumes FL+FR+FC+subwoof+BL+BR layout. */
icculus@10575
   107
    for (i = cvt->len_cvt / (sizeof (float) * 6); i; --i, src += 6, dst += 2) {
icculus@10793
   108
        const double front_center = (double) src[2];
icculus@10793
   109
        dst[0] = (float) ((src[0] + front_center + src[4]) / 3.0);  /* left */
icculus@10793
   110
        dst[1] = (float) ((src[1] + front_center + src[5]) / 3.0);  /* right */
icculus@1982
   111
    }
slouken@942
   112
slouken@1895
   113
    cvt->len_cvt /= 3;
slouken@1895
   114
    if (cvt->filters[++cvt->filter_index]) {
slouken@1895
   115
        cvt->filters[cvt->filter_index] (cvt, format);
slouken@1895
   116
    }
slouken@942
   117
}
slouken@942
   118
slouken@942
   119
icculus@10793
   120
/* Convert from 5.1 to quad */
icculus@1982
   121
static void SDLCALL
icculus@10793
   122
SDL_Convert51ToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
slouken@942
   123
{
icculus@10575
   124
    float *dst = (float *) cvt->buf;
icculus@10575
   125
    const float *src = dst;
slouken@1895
   126
    int i;
slouken@942
   127
icculus@10793
   128
    LOG_DEBUG_CONVERT("5.1", "quad");
icculus@10575
   129
    SDL_assert(format == AUDIO_F32SYS);
slouken@942
   130
icculus@10793
   131
    /* assumes quad is FL+FR+BL+BR layout and 5.1 is FL+FR+FC+subwoof+BL+BR */
icculus@10575
   132
    for (i = cvt->len_cvt / (sizeof (float) * 6); i; --i, src += 6, dst += 4) {
icculus@10793
   133
        /* FIXME: this is a good candidate for SIMD. */
icculus@10793
   134
        const double front_center = (double) src[2];
icculus@10793
   135
        dst[0] = (float) ((src[0] + front_center) * 0.5);  /* FL */
icculus@10793
   136
        dst[1] = (float) ((src[1] + front_center) * 0.5);  /* FR */
icculus@10793
   137
        dst[2] = (float) ((src[4] + front_center) * 0.5);  /* BL */
icculus@10793
   138
        dst[3] = (float) ((src[5] + front_center) * 0.5);  /* BR */
icculus@1982
   139
    }
slouken@942
   140
icculus@1982
   141
    cvt->len_cvt /= 6;
icculus@1982
   142
    cvt->len_cvt *= 4;
slouken@1895
   143
    if (cvt->filters[++cvt->filter_index]) {
slouken@1895
   144
        cvt->filters[cvt->filter_index] (cvt, format);
slouken@1895
   145
    }
slouken@942
   146
}
slouken@0
   147
icculus@10793
   148
slouken@0
   149
/* Duplicate a mono channel to both stereo channels */
icculus@1982
   150
static void SDLCALL
icculus@10793
   151
SDL_ConvertMonoToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
slouken@0
   152
{
icculus@10575
   153
    const float *src = (const float *) (cvt->buf + cvt->len_cvt);
icculus@10575
   154
    float *dst = (float *) (cvt->buf + cvt->len_cvt * 2);
slouken@1895
   155
    int i;
slouken@0
   156
icculus@10575
   157
    LOG_DEBUG_CONVERT("mono", "stereo");
icculus@10575
   158
    SDL_assert(format == AUDIO_F32SYS);
slouken@0
   159
icculus@10575
   160
    for (i = cvt->len_cvt / sizeof (float); i; --i) {
icculus@10575
   161
        src--;
icculus@10575
   162
        dst -= 2;
icculus@10575
   163
        dst[0] = dst[1] = *src;
icculus@1982
   164
    }
slouken@0
   165
slouken@1895
   166
    cvt->len_cvt *= 2;
slouken@1895
   167
    if (cvt->filters[++cvt->filter_index]) {
slouken@1895
   168
        cvt->filters[cvt->filter_index] (cvt, format);
slouken@1895
   169
    }
slouken@0
   170
}
slouken@0
   171
slouken@942
   172
slouken@942
   173
/* Duplicate a stereo channel to a pseudo-5.1 stream */
icculus@1982
   174
static void SDLCALL
icculus@10793
   175
SDL_ConvertStereoTo51(SDL_AudioCVT * cvt, SDL_AudioFormat format)
slouken@942
   176
{
slouken@1895
   177
    int i;
icculus@10575
   178
    float lf, rf, ce;
icculus@10575
   179
    const float *src = (const float *) (cvt->buf + cvt->len_cvt);
icculus@10575
   180
    float *dst = (float *) (cvt->buf + cvt->len_cvt * 3);
slouken@942
   181
icculus@10575
   182
    LOG_DEBUG_CONVERT("stereo", "5.1");
icculus@10575
   183
    SDL_assert(format == AUDIO_F32SYS);
slouken@942
   184
icculus@10575
   185
    for (i = cvt->len_cvt / 8; i; --i) {
icculus@10575
   186
        dst -= 6;
icculus@10575
   187
        src -= 2;
icculus@10575
   188
        lf = src[0];
icculus@10575
   189
        rf = src[1];
icculus@10793
   190
        ce = (lf + rf) * 0.5f;
icculus@10793
   191
        dst[0] = lf + (lf - ce);  /* FL */
icculus@10793
   192
        dst[1] = rf + (rf - ce);  /* FR */
icculus@10793
   193
        dst[2] = ce;  /* FC */
icculus@10793
   194
        dst[3] = ce;  /* !!! FIXME: wrong! This is the subwoofer. */
icculus@10793
   195
        dst[4] = lf;  /* BL */
icculus@10793
   196
        dst[5] = rf;  /* BR */
icculus@10575
   197
    }
slouken@942
   198
slouken@1895
   199
    cvt->len_cvt *= 3;
slouken@1895
   200
    if (cvt->filters[++cvt->filter_index]) {
slouken@1895
   201
        cvt->filters[cvt->filter_index] (cvt, format);
slouken@1895
   202
    }
slouken@942
   203
}
slouken@942
   204
slouken@942
   205
slouken@942
   206
/* Duplicate a stereo channel to a pseudo-4.0 stream */
icculus@1982
   207
static void SDLCALL
icculus@10793
   208
SDL_ConvertStereoToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
slouken@942
   209
{
icculus@10575
   210
    const float *src = (const float *) (cvt->buf + cvt->len_cvt);
icculus@10575
   211
    float *dst = (float *) (cvt->buf + cvt->len_cvt * 2);
icculus@10793
   212
    float lf, rf;
slouken@1895
   213
    int i;
slouken@942
   214
icculus@10575
   215
    LOG_DEBUG_CONVERT("stereo", "quad");
icculus@10575
   216
    SDL_assert(format == AUDIO_F32SYS);
slouken@942
   217
icculus@10575
   218
    for (i = cvt->len_cvt / 8; i; --i) {
icculus@10575
   219
        dst -= 4;
icculus@10575
   220
        src -= 2;
icculus@10575
   221
        lf = src[0];
icculus@10575
   222
        rf = src[1];
icculus@10793
   223
        dst[0] = lf;  /* FL */
icculus@10793
   224
        dst[1] = rf;  /* FR */
icculus@10793
   225
        dst[2] = lf;  /* BL */
icculus@10793
   226
        dst[3] = rf;  /* BR */
icculus@10575
   227
    }
slouken@942
   228
slouken@1895
   229
    cvt->len_cvt *= 2;
slouken@1895
   230
    if (cvt->filters[++cvt->filter_index]) {
slouken@1895
   231
        cvt->filters[cvt->filter_index] (cvt, format);
slouken@1895
   232
    }
slouken@0
   233
}
slouken@0
   234
icculus@10799
   235
static int
icculus@10799
   236
SDL_ResampleAudioSimple(const int chans, const double rate_incr,
icculus@10799
   237
                        float *last_sample, const float *inbuf,
icculus@10799
   238
                        const int inbuflen, float *outbuf, const int outbuflen)
icculus@10799
   239
{
icculus@10817
   240
    const int framelen = chans * (int)sizeof (float);
icculus@10799
   241
    const int total = (inbuflen / framelen);
icculus@10817
   242
    const int finalpos = (total * chans) - chans;
icculus@10817
   243
    const int dest_samples = (int)(((double)total) * rate_incr);
icculus@10799
   244
    const double src_incr = 1.0 / rate_incr;
icculus@10833
   245
    float *dst;
icculus@10833
   246
    double idx;
icculus@10799
   247
    int i;
icculus@10799
   248
icculus@10817
   249
    SDL_assert((dest_samples * framelen) <= outbuflen);
icculus@10799
   250
    SDL_assert((inbuflen % framelen) == 0);
icculus@10799
   251
icculus@10840
   252
    if (rate_incr > 1.0) {  /* upsample */
icculus@10833
   253
        float *target = (outbuf + chans);
icculus@10833
   254
        dst = outbuf + (dest_samples * chans);
icculus@10833
   255
        idx = (double) total;
icculus@10833
   256
icculus@10840
   257
        if (chans == 1) {
icculus@10840
   258
            const float final_sample = inbuf[finalpos];
icculus@10840
   259
            float earlier_sample = inbuf[finalpos];
icculus@10840
   260
            while (dst > target) {
icculus@10840
   261
                const int pos = ((int) idx) * chans;
icculus@10840
   262
                const float *src = &inbuf[pos];
icculus@10840
   263
                const float val = *(--src);
icculus@10840
   264
                SDL_assert(pos >= 0.0);
icculus@10840
   265
                *(--dst) = (val + earlier_sample) * 0.5f;
icculus@10840
   266
                earlier_sample = val;
icculus@10840
   267
                idx -= src_incr;
icculus@10840
   268
            }
icculus@10840
   269
            /* do last sample, interpolated against previous run's state. */
icculus@10840
   270
            *(--dst) = (inbuf[0] + last_sample[0]) * 0.5f;
icculus@10840
   271
            *last_sample = final_sample;
icculus@10840
   272
        } else if (chans == 2) {
icculus@10840
   273
            const float final_sample2 = inbuf[finalpos+1];
icculus@10840
   274
            const float final_sample1 = inbuf[finalpos];
icculus@10840
   275
            float earlier_sample2 = inbuf[finalpos];
icculus@10840
   276
            float earlier_sample1 = inbuf[finalpos-1];
icculus@10840
   277
            while (dst > target) {
icculus@10840
   278
                const int pos = ((int) idx) * chans;
icculus@10840
   279
                const float *src = &inbuf[pos];
icculus@10840
   280
                const float val2 = *(--src);
icculus@10840
   281
                const float val1 = *(--src);
icculus@10840
   282
                SDL_assert(pos >= 0.0);
icculus@10840
   283
                *(--dst) = (val2 + earlier_sample2) * 0.5f;
icculus@10840
   284
                *(--dst) = (val1 + earlier_sample1) * 0.5f;
icculus@10840
   285
                earlier_sample2 = val2;
icculus@10840
   286
                earlier_sample1 = val1;
icculus@10840
   287
                idx -= src_incr;
icculus@10840
   288
            }
icculus@10840
   289
            /* do last sample, interpolated against previous run's state. */
icculus@10840
   290
            *(--dst) = (inbuf[1] + last_sample[1]) * 0.5f;
icculus@10840
   291
            *(--dst) = (inbuf[0] + last_sample[0]) * 0.5f;
icculus@10840
   292
            last_sample[1] = final_sample2;
icculus@10840
   293
            last_sample[0] = final_sample1;
icculus@10840
   294
        } else {
icculus@10840
   295
            const float *earlier_sample = &inbuf[finalpos];
icculus@10840
   296
            float final_sample[8];
icculus@10840
   297
            SDL_memcpy(final_sample, &inbuf[finalpos], framelen);
icculus@10840
   298
            while (dst > target) {
icculus@10840
   299
                const int pos = ((int) idx) * chans;
icculus@10840
   300
                const float *src = &inbuf[pos];
icculus@10840
   301
                SDL_assert(pos >= 0.0);
icculus@10840
   302
                for (i = chans - 1; i >= 0; i--) {
icculus@10840
   303
                    const float val = *(--src);
icculus@10840
   304
                    *(--dst) = (val + earlier_sample[i]) * 0.5f;
icculus@10840
   305
                }
icculus@10840
   306
                earlier_sample = src;
icculus@10840
   307
                idx -= src_incr;
icculus@10840
   308
            }
icculus@10840
   309
            /* do last sample, interpolated against previous run's state. */
icculus@10833
   310
            for (i = chans - 1; i >= 0; i--) {
icculus@10840
   311
                const float val = inbuf[i];
icculus@10840
   312
                *(--dst) = (val + last_sample[i]) * 0.5f;
icculus@10833
   313
            }
icculus@10840
   314
            SDL_memcpy(last_sample, final_sample, framelen);
icculus@10799
   315
        }
icculus@10833
   316
icculus@10841
   317
        dst = (outbuf + (dest_samples * chans));
icculus@10840
   318
    } else {  /* downsample */
icculus@10833
   319
        float *target = (outbuf + (dest_samples * chans));
icculus@10833
   320
        dst = outbuf;
icculus@10833
   321
        idx = 0.0;
icculus@10840
   322
        if (chans == 1) {
icculus@10840
   323
            float last = *last_sample;
icculus@10840
   324
            while (dst < target) {
icculus@10840
   325
                const int pos = ((int) idx) * chans;
icculus@10840
   326
                const float val = inbuf[pos];
icculus@10840
   327
                SDL_assert(pos <= finalpos);
icculus@10840
   328
                *(dst++) = (val + last) * 0.5f;
icculus@10840
   329
                last = val;
icculus@10840
   330
                idx += src_incr;
icculus@10833
   331
            }
icculus@10840
   332
            *last_sample = last;
icculus@10840
   333
        } else if (chans == 2) {
icculus@10840
   334
            float last1 = last_sample[0];
icculus@10840
   335
            float last2 = last_sample[1];
icculus@10840
   336
            while (dst < target) {
icculus@10840
   337
                const int pos = ((int) idx) * chans;
icculus@10840
   338
                const float val1 = inbuf[pos];
icculus@10840
   339
                const float val2 = inbuf[pos+1];
icculus@10840
   340
                SDL_assert(pos <= finalpos);
icculus@10840
   341
                *(dst++) = (val1 + last1) * 0.5f;
icculus@10840
   342
                *(dst++) = (val2 + last2) * 0.5f;
icculus@10840
   343
                last1 = val1;
icculus@10840
   344
                last2 = val2;
icculus@10840
   345
                idx += src_incr;
icculus@10840
   346
            }
icculus@10840
   347
            last_sample[0] = last1;
icculus@10840
   348
            last_sample[1] = last2;
icculus@10840
   349
        } else {
icculus@10840
   350
            while (dst < target) {
icculus@10840
   351
                const int pos = ((int) idx) * chans;
icculus@10840
   352
                const float *src = &inbuf[pos];
icculus@10840
   353
                SDL_assert(pos <= finalpos);
icculus@10840
   354
                for (i = 0; i < chans; i++) {
icculus@10840
   355
                    const float val = *(src++);
icculus@10840
   356
                    *(dst++) = (val + last_sample[i]) * 0.5f;
icculus@10840
   357
                    last_sample[i] = val;
icculus@10840
   358
                }
icculus@10840
   359
                idx += src_incr;
icculus@10840
   360
            }
icculus@10833
   361
        }
icculus@10799
   362
    }
icculus@10799
   363
icculus@10833
   364
    return (int) ((dst - outbuf) * ((int) sizeof (float)));
icculus@10799
   365
}
icculus@10799
   366
icculus@10834
   367
/* We keep one special-case fast path around for an extremely common audio format. */
icculus@10834
   368
static int
icculus@10834
   369
SDL_ResampleAudioSimple_si16_c2(const double rate_incr,
icculus@10834
   370
                        Sint16 *last_sample, const Sint16 *inbuf,
icculus@10834
   371
                        const int inbuflen, Sint16 *outbuf, const int outbuflen)
icculus@10834
   372
{
icculus@10834
   373
    const int chans = 2;
icculus@10834
   374
    const int framelen = 4;  /* stereo 16 bit */
icculus@10834
   375
    const int total = (inbuflen / framelen);
icculus@10834
   376
    const int finalpos = (total * chans) - chans;
icculus@10834
   377
    const int dest_samples = (int)(((double)total) * rate_incr);
icculus@10834
   378
    const double src_incr = 1.0 / rate_incr;
icculus@10834
   379
    Sint16 *dst;
icculus@10834
   380
    double idx;
icculus@10834
   381
icculus@10834
   382
    SDL_assert((dest_samples * framelen) <= outbuflen);
icculus@10834
   383
    SDL_assert((inbuflen % framelen) == 0);
icculus@10834
   384
icculus@10834
   385
    if (rate_incr > 1.0) {
icculus@10834
   386
        Sint16 *target = (outbuf + chans);
icculus@10834
   387
        const Sint16 final_right = inbuf[finalpos+1];
icculus@10834
   388
        const Sint16 final_left = inbuf[finalpos];
icculus@10834
   389
        Sint16 earlier_right = inbuf[finalpos-1];
icculus@10834
   390
        Sint16 earlier_left = inbuf[finalpos-2];
icculus@10834
   391
        dst = outbuf + (dest_samples * chans);
icculus@10834
   392
        idx = (double) total;
icculus@10834
   393
icculus@10834
   394
        while (dst > target) {
icculus@10834
   395
            const int pos = ((int) idx) * chans;
icculus@10834
   396
            const Sint16 *src = &inbuf[pos];
icculus@10834
   397
            const Sint16 right = *(--src);
icculus@10834
   398
            const Sint16 left = *(--src);
icculus@10834
   399
            SDL_assert(pos >= 0.0);
icculus@10834
   400
            *(--dst) = (((Sint32) right) + ((Sint32) earlier_right)) >> 1;
icculus@10834
   401
            *(--dst) = (((Sint32) left) + ((Sint32) earlier_left)) >> 1;
icculus@10834
   402
            earlier_right = right;
icculus@10834
   403
            earlier_left = left;
icculus@10834
   404
            idx -= src_incr;
icculus@10834
   405
        }
icculus@10834
   406
icculus@10834
   407
        /* do last sample, interpolated against previous run's state. */
icculus@10834
   408
        *(--dst) = (((Sint32) inbuf[1]) + ((Sint32) last_sample[1])) >> 1;
icculus@10834
   409
        *(--dst) = (((Sint32) inbuf[0]) + ((Sint32) last_sample[0])) >> 1;
icculus@10834
   410
        last_sample[1] = final_right;
icculus@10834
   411
        last_sample[0] = final_left;
icculus@10834
   412
icculus@10841
   413
        dst = (outbuf + (dest_samples * chans));
icculus@10834
   414
    } else {
icculus@10834
   415
        Sint16 *target = (outbuf + (dest_samples * chans));
icculus@10834
   416
        dst = outbuf;
icculus@10834
   417
        idx = 0.0;
icculus@10834
   418
        while (dst < target) {
icculus@10834
   419
            const int pos = ((int) idx) * chans;
icculus@10834
   420
            const Sint16 *src = &inbuf[pos];
icculus@10834
   421
            const Sint16 left = *(src++);
icculus@10834
   422
            const Sint16 right = *(src++);
icculus@10834
   423
            SDL_assert(pos <= finalpos);
icculus@10834
   424
            *(dst++) = (((Sint32) left) + ((Sint32) last_sample[0])) >> 1;
icculus@10834
   425
            *(dst++) = (((Sint32) right) + ((Sint32) last_sample[1])) >> 1;
icculus@10834
   426
            last_sample[0] = left;
icculus@10834
   427
            last_sample[1] = right;
icculus@10834
   428
            idx += src_incr;
icculus@10834
   429
        }
icculus@10834
   430
    }
icculus@10834
   431
icculus@10834
   432
    return (int) ((dst - outbuf) * ((int) sizeof (Sint16)));
icculus@10834
   433
}
icculus@10834
   434
icculus@10834
   435
static void SDLCALL
icculus@10834
   436
SDL_ResampleCVT_si16_c2(SDL_AudioCVT *cvt, SDL_AudioFormat format)
icculus@10834
   437
{
icculus@10834
   438
    const Sint16 *src = (const Sint16 *) cvt->buf;
icculus@10834
   439
    const int srclen = cvt->len_cvt;
icculus@10839
   440
    Sint16 *dst = (Sint16 *) cvt->buf;
icculus@10839
   441
    const int dstlen = (cvt->len * cvt->len_mult);
icculus@10925
   442
    Sint16 state[2];
icculus@10925
   443
icculus@10925
   444
    state[0] = src[0];
icculus@10925
   445
    state[1] = src[1];
icculus@10834
   446
icculus@10834
   447
    SDL_assert(format == AUDIO_S16SYS);
icculus@10834
   448
icculus@10834
   449
    cvt->len_cvt = SDL_ResampleAudioSimple_si16_c2(cvt->rate_incr, state, src, srclen, dst, dstlen);
icculus@10834
   450
    if (cvt->filters[++cvt->filter_index]) {
icculus@10834
   451
        cvt->filters[cvt->filter_index](cvt, format);
icculus@10834
   452
    }
icculus@10834
   453
}
icculus@10834
   454
slouken@0
   455
slouken@1895
   456
int
slouken@1895
   457
SDL_ConvertAudio(SDL_AudioCVT * cvt)
slouken@0
   458
{
icculus@3021
   459
    /* !!! FIXME: (cvt) should be const; stack-copy it here. */
icculus@3021
   460
    /* !!! FIXME: (actually, we can't...len_cvt needs to be updated. Grr.) */
icculus@3021
   461
slouken@1895
   462
    /* Make sure there's data to convert */
slouken@1895
   463
    if (cvt->buf == NULL) {
icculus@10575
   464
        return SDL_SetError("No buffer allocated for conversion");
slouken@1895
   465
    }
icculus@10575
   466
slouken@1895
   467
    /* Return okay if no conversion is necessary */
slouken@1895
   468
    cvt->len_cvt = cvt->len;
slouken@1895
   469
    if (cvt->filters[0] == NULL) {
icculus@10575
   470
        return 0;
slouken@1895
   471
    }
slouken@0
   472
slouken@1895
   473
    /* Set up the conversion and go! */
slouken@1895
   474
    cvt->filter_index = 0;
slouken@1895
   475
    cvt->filters[0] (cvt, cvt->src_format);
icculus@10575
   476
    return 0;
slouken@0
   477
}
slouken@0
   478
icculus@10575
   479
static void SDLCALL
icculus@10575
   480
SDL_Convert_Byteswap(SDL_AudioCVT *cvt, SDL_AudioFormat format)
icculus@10575
   481
{
slouken@10579
   482
#if DEBUG_CONVERT
slouken@10579
   483
    printf("Converting byte order\n");
slouken@10579
   484
#endif
icculus@1982
   485
icculus@10575
   486
    switch (SDL_AUDIO_BITSIZE(format)) {
icculus@10575
   487
        #define CASESWAP(b) \
icculus@10575
   488
            case b: { \
icculus@10575
   489
                Uint##b *ptr = (Uint##b *) cvt->buf; \
icculus@10575
   490
                int i; \
icculus@10575
   491
                for (i = cvt->len_cvt / sizeof (*ptr); i; --i, ++ptr) { \
icculus@10575
   492
                    *ptr = SDL_Swap##b(*ptr); \
icculus@10575
   493
                } \
icculus@10575
   494
                break; \
icculus@10575
   495
            }
icculus@1982
   496
icculus@10575
   497
        CASESWAP(16);
icculus@10575
   498
        CASESWAP(32);
icculus@10575
   499
        CASESWAP(64);
icculus@10575
   500
icculus@10575
   501
        #undef CASESWAP
icculus@10575
   502
icculus@10575
   503
        default: SDL_assert(!"unhandled byteswap datatype!"); break;
icculus@10575
   504
    }
icculus@10575
   505
icculus@10575
   506
    if (cvt->filters[++cvt->filter_index]) {
icculus@10575
   507
        /* flip endian flag for data. */
icculus@10575
   508
        if (format & SDL_AUDIO_MASK_ENDIAN) {
icculus@10575
   509
            format &= ~SDL_AUDIO_MASK_ENDIAN;
icculus@10575
   510
        } else {
icculus@10575
   511
            format |= SDL_AUDIO_MASK_ENDIAN;
icculus@10575
   512
        }
icculus@10575
   513
        cvt->filters[cvt->filter_index](cvt, format);
icculus@10575
   514
    }
icculus@1982
   515
}
icculus@1982
   516
slouken@11096
   517
static int
slouken@11096
   518
SDL_AddAudioCVTFilter(SDL_AudioCVT *cvt, const SDL_AudioFilter filter)
slouken@11096
   519
{
slouken@11096
   520
    if (cvt->filter_index >= SDL_AUDIOCVT_MAX_FILTERS) {
slouken@11096
   521
        return SDL_SetError("Too many filters needed for conversion, exceeded maximum of %d", SDL_AUDIOCVT_MAX_FILTERS);
slouken@11096
   522
    }
slouken@11096
   523
    if (filter == NULL) {
slouken@11096
   524
        return SDL_SetError("Audio filter pointer is NULL");
slouken@11096
   525
    }
slouken@11096
   526
    cvt->filters[cvt->filter_index++] = filter;
slouken@11096
   527
    cvt->filters[cvt->filter_index] = NULL; /* Moving terminator */
slouken@11096
   528
    return 0;
slouken@11096
   529
}
icculus@1982
   530
icculus@1982
   531
static int
icculus@10575
   532
SDL_BuildAudioTypeCVTToFloat(SDL_AudioCVT *cvt, const SDL_AudioFormat src_fmt)
icculus@1982
   533
{
icculus@10575
   534
    int retval = 0;  /* 0 == no conversion necessary. */
icculus@1982
   535
icculus@10575
   536
    if ((SDL_AUDIO_ISBIGENDIAN(src_fmt) != 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN)) {
slouken@11096
   537
        if (SDL_AddAudioCVTFilter(cvt, SDL_Convert_Byteswap) < 0) {
slouken@11096
   538
            return -1;
slouken@11096
   539
        }
icculus@10575
   540
        retval = 1;  /* added a converter. */
icculus@10575
   541
    }
icculus@1982
   542
icculus@10575
   543
    if (!SDL_AUDIO_ISFLOAT(src_fmt)) {
icculus@10576
   544
        const Uint16 src_bitsize = SDL_AUDIO_BITSIZE(src_fmt);
icculus@10576
   545
        const Uint16 dst_bitsize = 32;
icculus@10575
   546
        SDL_AudioFilter filter = NULL;
icculus@10576
   547
icculus@10575
   548
        switch (src_fmt & ~SDL_AUDIO_MASK_ENDIAN) {
icculus@10575
   549
            case AUDIO_S8: filter = SDL_Convert_S8_to_F32; break;
icculus@10575
   550
            case AUDIO_U8: filter = SDL_Convert_U8_to_F32; break;
icculus@10575
   551
            case AUDIO_S16: filter = SDL_Convert_S16_to_F32; break;
philipp@10591
   552
            case AUDIO_U16: filter = SDL_Convert_U16_to_F32; break;
icculus@10575
   553
            case AUDIO_S32: filter = SDL_Convert_S32_to_F32; break;
icculus@10575
   554
            default: SDL_assert(!"Unexpected audio format!"); break;
icculus@1982
   555
        }
icculus@1982
   556
icculus@10575
   557
        if (!filter) {
icculus@10575
   558
            return SDL_SetError("No conversion available for these formats");
icculus@10575
   559
        }
icculus@10575
   560
slouken@11096
   561
        if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
slouken@11096
   562
            return -1;
slouken@11096
   563
        }
icculus@1982
   564
        if (src_bitsize < dst_bitsize) {
icculus@1982
   565
            const int mult = (dst_bitsize / src_bitsize);
icculus@1982
   566
            cvt->len_mult *= mult;
icculus@1982
   567
            cvt->len_ratio *= mult;
icculus@1982
   568
        } else if (src_bitsize > dst_bitsize) {
icculus@1982
   569
            cvt->len_ratio /= (src_bitsize / dst_bitsize);
icculus@1982
   570
        }
icculus@10576
   571
icculus@10575
   572
        retval = 1;  /* added a converter. */
icculus@1982
   573
    }
icculus@1982
   574
icculus@10575
   575
    return retval;
icculus@1982
   576
}
icculus@1982
   577
icculus@10575
   578
static int
icculus@10575
   579
SDL_BuildAudioTypeCVTFromFloat(SDL_AudioCVT *cvt, const SDL_AudioFormat dst_fmt)
icculus@10575
   580
{
icculus@10575
   581
    int retval = 0;  /* 0 == no conversion necessary. */
icculus@3021
   582
icculus@10575
   583
    if (!SDL_AUDIO_ISFLOAT(dst_fmt)) {
icculus@10577
   584
        const Uint16 dst_bitsize = SDL_AUDIO_BITSIZE(dst_fmt);
icculus@10577
   585
        const Uint16 src_bitsize = 32;
icculus@10575
   586
        SDL_AudioFilter filter = NULL;
icculus@10575
   587
        switch (dst_fmt & ~SDL_AUDIO_MASK_ENDIAN) {
icculus@10575
   588
            case AUDIO_S8: filter = SDL_Convert_F32_to_S8; break;
icculus@10575
   589
            case AUDIO_U8: filter = SDL_Convert_F32_to_U8; break;
icculus@10575
   590
            case AUDIO_S16: filter = SDL_Convert_F32_to_S16; break;
philipp@10591
   591
            case AUDIO_U16: filter = SDL_Convert_F32_to_U16; break;
icculus@10575
   592
            case AUDIO_S32: filter = SDL_Convert_F32_to_S32; break;
icculus@10575
   593
            default: SDL_assert(!"Unexpected audio format!"); break;
icculus@10575
   594
        }
slouken@2716
   595
icculus@10575
   596
        if (!filter) {
icculus@10575
   597
            return SDL_SetError("No conversion available for these formats");
icculus@10575
   598
        }
icculus@10575
   599
slouken@11096
   600
        if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
slouken@11096
   601
            return -1;
slouken@11096
   602
        }
icculus@10575
   603
        if (src_bitsize < dst_bitsize) {
icculus@10575
   604
            const int mult = (dst_bitsize / src_bitsize);
icculus@10575
   605
            cvt->len_mult *= mult;
icculus@10575
   606
            cvt->len_ratio *= mult;
icculus@10575
   607
        } else if (src_bitsize > dst_bitsize) {
icculus@10575
   608
            cvt->len_ratio /= (src_bitsize / dst_bitsize);
icculus@10575
   609
        }
icculus@10575
   610
        retval = 1;  /* added a converter. */
icculus@10575
   611
    }
icculus@10575
   612
icculus@10575
   613
    if ((SDL_AUDIO_ISBIGENDIAN(dst_fmt) != 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN)) {
slouken@11096
   614
        if (SDL_AddAudioCVTFilter(cvt, SDL_Convert_Byteswap) < 0) {
slouken@11096
   615
            return -1;
slouken@11096
   616
        }
icculus@10575
   617
        retval = 1;  /* added a converter. */
icculus@10575
   618
    }
icculus@10575
   619
icculus@10575
   620
    return retval;
icculus@3021
   621
}
slouken@2716
   622
icculus@10799
   623
static void
icculus@10799
   624
SDL_ResampleCVT(SDL_AudioCVT *cvt, const int chans, const SDL_AudioFormat format)
icculus@10799
   625
{
icculus@10799
   626
    const float *src = (const float *) cvt->buf;
icculus@10799
   627
    const int srclen = cvt->len_cvt;
icculus@10833
   628
    float *dst = (float *) cvt->buf;
icculus@10833
   629
    const int dstlen = (cvt->len * cvt->len_mult);
icculus@10804
   630
    float state[8];
icculus@10756
   631
icculus@10799
   632
    SDL_assert(format == AUDIO_F32SYS);
icculus@10799
   633
slouken@10805
   634
    SDL_memcpy(state, src, chans*sizeof(*src));
icculus@10799
   635
icculus@10804
   636
    cvt->len_cvt = SDL_ResampleAudioSimple(chans, cvt->rate_incr, state, src, srclen, dst, dstlen);
icculus@10799
   637
    if (cvt->filters[++cvt->filter_index]) {
icculus@10799
   638
        cvt->filters[cvt->filter_index](cvt, format);
icculus@10799
   639
    }
icculus@10799
   640
}
icculus@10799
   641
icculus@10799
   642
/* !!! FIXME: We only have this macro salsa because SDL_AudioCVT doesn't
icculus@10799
   643
   !!! FIXME:  store channel info, so we have to have function entry
icculus@10799
   644
   !!! FIXME:  points for each supported channel count and multiple
icculus@10799
   645
   !!! FIXME:  vs arbitrary. When we rev the ABI, clean this up. */
icculus@10756
   646
#define RESAMPLER_FUNCS(chans) \
icculus@10756
   647
    static void SDLCALL \
icculus@10799
   648
    SDL_ResampleCVT_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
icculus@10799
   649
        SDL_ResampleCVT(cvt, chans, format); \
icculus@10756
   650
    }
icculus@10756
   651
RESAMPLER_FUNCS(1)
icculus@10756
   652
RESAMPLER_FUNCS(2)
icculus@10756
   653
RESAMPLER_FUNCS(4)
icculus@10756
   654
RESAMPLER_FUNCS(6)
icculus@10756
   655
RESAMPLER_FUNCS(8)
icculus@10756
   656
#undef RESAMPLER_FUNCS
icculus@10756
   657
icculus@10799
   658
static SDL_AudioFilter
icculus@10799
   659
ChooseCVTResampler(const int dst_channels)
icculus@3021
   660
{
icculus@10799
   661
    switch (dst_channels) {
icculus@10799
   662
        case 1: return SDL_ResampleCVT_c1;
icculus@10799
   663
        case 2: return SDL_ResampleCVT_c2;
icculus@10799
   664
        case 4: return SDL_ResampleCVT_c4;
icculus@10799
   665
        case 6: return SDL_ResampleCVT_c6;
icculus@10799
   666
        case 8: return SDL_ResampleCVT_c8;
icculus@10799
   667
        default: break;
icculus@3021
   668
    }
slouken@2716
   669
icculus@10799
   670
    return NULL;
icculus@10756
   671
}
icculus@10575
   672
icculus@3021
   673
static int
icculus@10756
   674
SDL_BuildAudioResampleCVT(SDL_AudioCVT * cvt, const int dst_channels,
icculus@10756
   675
                          const int src_rate, const int dst_rate)
icculus@3021
   676
{
icculus@10756
   677
    SDL_AudioFilter filter;
icculus@3021
   678
icculus@10756
   679
    if (src_rate == dst_rate) {
icculus@10756
   680
        return 0;  /* no conversion necessary. */
slouken@2716
   681
    }
slouken@2716
   682
icculus@10799
   683
    filter = ChooseCVTResampler(dst_channels);
icculus@10756
   684
    if (filter == NULL) {
icculus@10756
   685
        return SDL_SetError("No conversion available for these rates");
icculus@10756
   686
    }
icculus@10756
   687
icculus@10756
   688
    /* Update (cvt) with filter details... */
slouken@11096
   689
    if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
slouken@11096
   690
        return -1;
slouken@11096
   691
    }
icculus@10756
   692
    if (src_rate < dst_rate) {
icculus@10756
   693
        const double mult = ((double) dst_rate) / ((double) src_rate);
icculus@10756
   694
        cvt->len_mult *= (int) SDL_ceil(mult);
icculus@10756
   695
        cvt->len_ratio *= mult;
icculus@10756
   696
    } else {
icculus@10756
   697
        cvt->len_ratio /= ((double) src_rate) / ((double) dst_rate);
icculus@10756
   698
    }
icculus@10756
   699
icculus@10756
   700
    return 1;               /* added a converter. */
slouken@2716
   701
}
icculus@1982
   702
icculus@1982
   703
icculus@1982
   704
/* Creates a set of audio filters to convert from one format to another.
icculus@1982
   705
   Returns -1 if the format conversion is not supported, 0 if there's
icculus@1982
   706
   no conversion needed, or 1 if the audio filter is set up.
slouken@0
   707
*/
slouken@1895
   708
slouken@1895
   709
int
slouken@1895
   710
SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
icculus@1982
   711
                  SDL_AudioFormat src_fmt, Uint8 src_channels, int src_rate,
icculus@1982
   712
                  SDL_AudioFormat dst_fmt, Uint8 dst_channels, int dst_rate)
slouken@0
   713
{
aschiffler@6819
   714
    /* Sanity check target pointer */
aschiffler@6819
   715
    if (cvt == NULL) {
icculus@7037
   716
        return SDL_InvalidParamError("cvt");
aschiffler@6819
   717
    }
slouken@7191
   718
slouken@10767
   719
    /* Make sure we zero out the audio conversion before error checking */
slouken@10767
   720
    SDL_zerop(cvt);
slouken@10767
   721
slouken@3491
   722
    /* there are no unsigned types over 16 bits, so catch this up front. */
icculus@1982
   723
    if ((SDL_AUDIO_BITSIZE(src_fmt) > 16) && (!SDL_AUDIO_ISSIGNED(src_fmt))) {
icculus@7037
   724
        return SDL_SetError("Invalid source format");
icculus@1982
   725
    }
icculus@1982
   726
    if ((SDL_AUDIO_BITSIZE(dst_fmt) > 16) && (!SDL_AUDIO_ISSIGNED(dst_fmt))) {
icculus@7037
   727
        return SDL_SetError("Invalid destination format");
icculus@1982
   728
    }
icculus@3021
   729
icculus@3021
   730
    /* prevent possible divisions by zero, etc. */
aschiffler@6819
   731
    if ((src_channels == 0) || (dst_channels == 0)) {
icculus@7037
   732
        return SDL_SetError("Source or destination channels is zero");
aschiffler@6819
   733
    }
icculus@3021
   734
    if ((src_rate == 0) || (dst_rate == 0)) {
icculus@7037
   735
        return SDL_SetError("Source or destination rate is zero");
icculus@3021
   736
    }
slouken@10579
   737
#if DEBUG_CONVERT
icculus@1982
   738
    printf("Build format %04x->%04x, channels %u->%u, rate %d->%d\n",
slouken@1985
   739
           src_fmt, dst_fmt, src_channels, dst_channels, src_rate, dst_rate);
slouken@1985
   740
#endif
icculus@1982
   741
slouken@1895
   742
    /* Start off with no conversion necessary */
icculus@1982
   743
    cvt->src_format = src_fmt;
icculus@1982
   744
    cvt->dst_format = dst_fmt;
slouken@1895
   745
    cvt->needed = 0;
slouken@1895
   746
    cvt->filter_index = 0;
slouken@1895
   747
    cvt->filters[0] = NULL;
slouken@1895
   748
    cvt->len_mult = 1;
slouken@1895
   749
    cvt->len_ratio = 1.0;
icculus@3021
   750
    cvt->rate_incr = ((double) dst_rate) / ((double) src_rate);
slouken@0
   751
icculus@10834
   752
    /* SDL now favors float32 as its preferred internal format, and considers
icculus@10834
   753
       everything else to be a degenerate case that we might have to make
icculus@10834
   754
       multiple passes over the data to convert to and from float32 as
icculus@10834
   755
       necessary. That being said, we keep one special case around for
icculus@10834
   756
       efficiency: stereo data in Sint16 format, in the native byte order,
icculus@10834
   757
       that only needs resampling. This is likely to be the most popular
icculus@10834
   758
       legacy format, that apps, hardware and the OS are likely to be able
icculus@10834
   759
       to process directly, so we handle this one case directly without
icculus@10834
   760
       unnecessary conversions. This means that apps on embedded devices
icculus@10834
   761
       without floating point hardware should consider aiming for this
icculus@10834
   762
       format as well. */
icculus@10834
   763
    if ((src_channels == 2) && (dst_channels == 2) && (src_fmt == AUDIO_S16SYS) && (dst_fmt == AUDIO_S16SYS) && (src_rate != dst_rate)) {
icculus@10834
   764
        cvt->needed = 1;
slouken@11096
   765
        if (SDL_AddAudioCVTFilter(cvt, SDL_ResampleCVT_si16_c2) < 0) {
slouken@11096
   766
            return -1;
slouken@11096
   767
        }
icculus@10834
   768
        if (src_rate < dst_rate) {
icculus@10834
   769
            const double mult = ((double) dst_rate) / ((double) src_rate);
icculus@10834
   770
            cvt->len_mult *= (int) SDL_ceil(mult);
icculus@10834
   771
            cvt->len_ratio *= mult;
icculus@10834
   772
        } else {
icculus@10834
   773
            cvt->len_ratio /= ((double) src_rate) / ((double) dst_rate);
icculus@10834
   774
        }
icculus@10834
   775
        return 1;
icculus@10834
   776
    }
icculus@10834
   777
icculus@10575
   778
    /* Type conversion goes like this now:
icculus@10575
   779
        - byteswap to CPU native format first if necessary.
icculus@10575
   780
        - convert to native Float32 if necessary.
icculus@10575
   781
        - resample and change channel count if necessary.
icculus@10575
   782
        - convert back to native format.
icculus@10575
   783
        - byteswap back to foreign format if necessary.
icculus@10575
   784
icculus@10575
   785
       The expectation is we can process data faster in float32
icculus@10575
   786
       (possibly with SIMD), and making several passes over the same
icculus@10756
   787
       buffer is likely to be CPU cache-friendly, avoiding the
icculus@10575
   788
       biggest performance hit in modern times. Previously we had
icculus@10575
   789
       (script-generated) custom converters for every data type and
icculus@10575
   790
       it was a bloat on SDL compile times and final library size. */
icculus@10575
   791
slouken@10767
   792
    /* see if we can skip float conversion entirely. */
slouken@10767
   793
    if (src_rate == dst_rate && src_channels == dst_channels) {
slouken@10767
   794
        if (src_fmt == dst_fmt) {
slouken@10767
   795
            return 0;
slouken@10767
   796
        }
slouken@10767
   797
slouken@10767
   798
        /* just a byteswap needed? */
slouken@10767
   799
        if ((src_fmt & ~SDL_AUDIO_MASK_ENDIAN) == (dst_fmt & ~SDL_AUDIO_MASK_ENDIAN)) {
slouken@11096
   800
            if (SDL_AddAudioCVTFilter(cvt, SDL_Convert_Byteswap) < 0) {
slouken@11096
   801
                return -1;
slouken@11096
   802
            }
slouken@10767
   803
            cvt->needed = 1;
slouken@10767
   804
            return 1;
slouken@10767
   805
        }
icculus@10575
   806
    }
icculus@10575
   807
icculus@1982
   808
    /* Convert data types, if necessary. Updates (cvt). */
slouken@10767
   809
    if (SDL_BuildAudioTypeCVTToFloat(cvt, src_fmt) < 0) {
slouken@1985
   810
        return -1;              /* shouldn't happen, but just in case... */
icculus@3021
   811
    }
slouken@0
   812
icculus@1982
   813
    /* Channel conversion */
slouken@1895
   814
    if (src_channels != dst_channels) {
slouken@1895
   815
        if ((src_channels == 1) && (dst_channels > 1)) {
slouken@11096
   816
            if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertMonoToStereo) < 0) {
slouken@11096
   817
                return -1;
slouken@11096
   818
            }
slouken@1895
   819
            cvt->len_mult *= 2;
slouken@1895
   820
            src_channels = 2;
slouken@1895
   821
            cvt->len_ratio *= 2;
slouken@1895
   822
        }
slouken@1895
   823
        if ((src_channels == 2) && (dst_channels == 6)) {
slouken@11096
   824
            if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertStereoTo51) < 0) {
slouken@11096
   825
                return -1;
slouken@11096
   826
            }
slouken@1895
   827
            src_channels = 6;
slouken@1895
   828
            cvt->len_mult *= 3;
slouken@1895
   829
            cvt->len_ratio *= 3;
slouken@1895
   830
        }
slouken@1895
   831
        if ((src_channels == 2) && (dst_channels == 4)) {
slouken@11096
   832
            if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertStereoToQuad) < 0) {
slouken@11096
   833
                return -1;
slouken@11096
   834
            }
slouken@1895
   835
            src_channels = 4;
slouken@1895
   836
            cvt->len_mult *= 2;
slouken@1895
   837
            cvt->len_ratio *= 2;
slouken@1895
   838
        }
slouken@1895
   839
        while ((src_channels * 2) <= dst_channels) {
slouken@11096
   840
            if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertMonoToStereo) < 0) {
slouken@11096
   841
                return -1;
slouken@11096
   842
            }
slouken@1895
   843
            cvt->len_mult *= 2;
slouken@1895
   844
            src_channels *= 2;
slouken@1895
   845
            cvt->len_ratio *= 2;
slouken@1895
   846
        }
slouken@1895
   847
        if ((src_channels == 6) && (dst_channels <= 2)) {
slouken@11096
   848
            if (SDL_AddAudioCVTFilter(cvt, SDL_Convert51ToStereo) < 0) {
slouken@11096
   849
                return -1;
slouken@11096
   850
            }
slouken@1895
   851
            src_channels = 2;
slouken@1895
   852
            cvt->len_ratio /= 3;
slouken@1895
   853
        }
slouken@1895
   854
        if ((src_channels == 6) && (dst_channels == 4)) {
slouken@11096
   855
            if (SDL_AddAudioCVTFilter(cvt, SDL_Convert51ToQuad) < 0) {
slouken@11096
   856
                return -1;
slouken@11096
   857
            }
slouken@1895
   858
            src_channels = 4;
slouken@1895
   859
            cvt->len_ratio /= 2;
slouken@1895
   860
        }
slouken@1895
   861
        /* This assumes that 4 channel audio is in the format:
slouken@1895
   862
           Left {front/back} + Right {front/back}
slouken@1895
   863
           so converting to L/R stereo works properly.
slouken@1895
   864
         */
slouken@1895
   865
        while (((src_channels % 2) == 0) &&
slouken@1895
   866
               ((src_channels / 2) >= dst_channels)) {
icculus@10832
   867
            SDL_AudioFilter filter = NULL;
icculus@10832
   868
icculus@10832
   869
            #if HAVE_SSE3_INTRINSICS
icculus@10832
   870
            if (SDL_HasSSE3()) {
icculus@10832
   871
                filter = SDL_ConvertStereoToMono_SSE3;
icculus@10832
   872
            }
icculus@10832
   873
            #endif
icculus@10832
   874
icculus@10832
   875
            if (!filter) {
icculus@10832
   876
                filter = SDL_ConvertStereoToMono;
icculus@10832
   877
            }
icculus@10832
   878
slouken@11096
   879
            if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
slouken@11096
   880
                return -1;
slouken@11096
   881
            }
icculus@10832
   882
slouken@1895
   883
            src_channels /= 2;
slouken@1895
   884
            cvt->len_ratio /= 2;
slouken@1895
   885
        }
slouken@1895
   886
        if (src_channels != dst_channels) {
slouken@1895
   887
            /* Uh oh.. */ ;
slouken@1895
   888
        }
slouken@1895
   889
    }
slouken@0
   890
icculus@3021
   891
    /* Do rate conversion, if necessary. Updates (cvt). */
slouken@10767
   892
    if (SDL_BuildAudioResampleCVT(cvt, dst_channels, src_rate, dst_rate) < 0) {
icculus@3021
   893
        return -1;              /* shouldn't happen, but just in case... */
slouken@2716
   894
    }
slouken@2716
   895
icculus@10756
   896
    /* Move to final data type. */
slouken@10767
   897
    if (SDL_BuildAudioTypeCVTFromFloat(cvt, dst_fmt) < 0) {
icculus@10575
   898
        return -1;              /* shouldn't happen, but just in case... */
slouken@1895
   899
    }
icculus@10575
   900
icculus@10575
   901
    cvt->needed = (cvt->filter_index != 0);
slouken@1895
   902
    return (cvt->needed);
slouken@0
   903
}
slouken@1895
   904
icculus@10842
   905
typedef int (*SDL_ResampleAudioStreamFunc)(SDL_AudioStream *stream, const void *inbuf, const int inbuflen, void *outbuf, const int outbuflen);
slouken@10773
   906
typedef void (*SDL_ResetAudioStreamResamplerFunc)(SDL_AudioStream *stream);
slouken@10773
   907
typedef void (*SDL_CleanupAudioStreamResamplerFunc)(SDL_AudioStream *stream);
icculus@10757
   908
icculus@10757
   909
struct SDL_AudioStream
icculus@10757
   910
{
icculus@10757
   911
    SDL_AudioCVT cvt_before_resampling;
icculus@10757
   912
    SDL_AudioCVT cvt_after_resampling;
icculus@10757
   913
    SDL_DataQueue *queue;
icculus@10844
   914
    Uint8 *work_buffer_base;  /* maybe unaligned pointer from SDL_realloc(). */
icculus@10757
   915
    int work_buffer_len;
icculus@10757
   916
    int src_sample_frame_size;
icculus@10757
   917
    SDL_AudioFormat src_format;
icculus@10757
   918
    Uint8 src_channels;
icculus@10757
   919
    int src_rate;
icculus@10757
   920
    int dst_sample_frame_size;
icculus@10757
   921
    SDL_AudioFormat dst_format;
icculus@10757
   922
    Uint8 dst_channels;
icculus@10757
   923
    int dst_rate;
icculus@10757
   924
    double rate_incr;
icculus@10757
   925
    Uint8 pre_resample_channels;
slouken@10773
   926
    int packetlen;
slouken@10773
   927
    void *resampler_state;
slouken@10773
   928
    SDL_ResampleAudioStreamFunc resampler_func;
slouken@10773
   929
    SDL_ResetAudioStreamResamplerFunc reset_resampler_func;
slouken@10773
   930
    SDL_CleanupAudioStreamResamplerFunc cleanup_resampler_func;
slouken@10773
   931
};
slouken@10773
   932
icculus@10851
   933
static Uint8 *
icculus@10851
   934
EnsureStreamBufferSize(SDL_AudioStream *stream, const int newlen)
icculus@10851
   935
{
icculus@10851
   936
    Uint8 *ptr;
icculus@10851
   937
    size_t offset;
icculus@10851
   938
icculus@10851
   939
    if (stream->work_buffer_len >= newlen) {
icculus@10851
   940
        ptr = stream->work_buffer_base;
icculus@10851
   941
    } else {
icculus@10851
   942
        ptr = (Uint8 *) SDL_realloc(stream->work_buffer_base, newlen + 32);
icculus@10851
   943
        if (!ptr) {
icculus@10851
   944
            SDL_OutOfMemory();
icculus@10851
   945
            return NULL;
icculus@10851
   946
        }
icculus@10851
   947
        /* Make sure we're aligned to 16 bytes for SIMD code. */
icculus@10851
   948
        stream->work_buffer_base = ptr;
icculus@10851
   949
        stream->work_buffer_len = newlen;
icculus@10851
   950
    }
icculus@10851
   951
icculus@10851
   952
    offset = ((size_t) ptr) & 15;
icculus@10851
   953
    return offset ? ptr + (16 - offset) : ptr;
icculus@10851
   954
}
icculus@10851
   955
slouken@10777
   956
#ifdef HAVE_LIBSAMPLERATE_H
slouken@10773
   957
static int
icculus@10842
   958
SDL_ResampleAudioStream_SRC(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
slouken@10773
   959
{
icculus@10842
   960
    const float *inbuf = (const float *) _inbuf;
icculus@10842
   961
    float *outbuf = (float *) _outbuf;
icculus@10799
   962
    const int framelen = sizeof(float) * stream->pre_resample_channels;
icculus@10790
   963
    SRC_STATE *state = (SRC_STATE *)stream->resampler_state;
slouken@10773
   964
    SRC_DATA data;
slouken@10773
   965
    int result;
slouken@10773
   966
icculus@10851
   967
    if (inbuf == ((const float *) outbuf)) {  /* libsamplerate can't work in-place. */
icculus@10851
   968
        Uint8 *ptr = EnsureStreamBufferSize(stream, inbuflen + outbuflen);
icculus@10851
   969
        if (ptr == NULL) {
icculus@10851
   970
            SDL_OutOfMemory();
icculus@10851
   971
            return 0;
icculus@10851
   972
        }
icculus@10851
   973
        SDL_memcpy(ptr + outbuflen, ptr, inbuflen);
icculus@10851
   974
        inbuf = (const float *) (ptr + outbuflen);
icculus@10851
   975
        outbuf = (float *) ptr;
icculus@10851
   976
    }
icculus@10851
   977
slouken@10777
   978
    data.data_in = (float *)inbuf; /* Older versions of libsamplerate had a non-const pointer, but didn't write to it */
icculus@10799
   979
    data.input_frames = inbuflen / framelen;
slouken@10773
   980
    data.input_frames_used = 0;
slouken@10773
   981
slouken@10773
   982
    data.data_out = outbuf;
icculus@10799
   983
    data.output_frames = outbuflen / framelen;
slouken@10773
   984
slouken@10773
   985
    data.end_of_input = 0;
slouken@10773
   986
    data.src_ratio = stream->rate_incr;
slouken@10773
   987
icculus@10790
   988
    result = SRC_src_process(state, &data);
slouken@10773
   989
    if (result != 0) {
icculus@10790
   990
        SDL_SetError("src_process() failed: %s", SRC_src_strerror(result));
slouken@10773
   991
        return 0;
slouken@10773
   992
    }
slouken@10773
   993
slouken@10773
   994
    /* If this fails, we need to store them off somewhere */
slouken@10773
   995
    SDL_assert(data.input_frames_used == data.input_frames);
slouken@10773
   996
slouken@10773
   997
    return data.output_frames_gen * (sizeof(float) * stream->pre_resample_channels);
slouken@10773
   998
}
slouken@10773
   999
slouken@10773
  1000
static void
slouken@10773
  1001
SDL_ResetAudioStreamResampler_SRC(SDL_AudioStream *stream)
slouken@10773
  1002
{
icculus@10790
  1003
    SRC_src_reset((SRC_STATE *)stream->resampler_state);
slouken@10773
  1004
}
slouken@10773
  1005
slouken@10773
  1006
static void
slouken@10773
  1007
SDL_CleanupAudioStreamResampler_SRC(SDL_AudioStream *stream)
slouken@10773
  1008
{
icculus@10790
  1009
    SRC_STATE *state = (SRC_STATE *)stream->resampler_state;
slouken@10773
  1010
    if (state) {
icculus@10790
  1011
        SRC_src_delete(state);
slouken@10773
  1012
    }
slouken@10773
  1013
slouken@10773
  1014
    stream->resampler_state = NULL;
slouken@10773
  1015
    stream->resampler_func = NULL;
slouken@10773
  1016
    stream->reset_resampler_func = NULL;
slouken@10773
  1017
    stream->cleanup_resampler_func = NULL;
slouken@10773
  1018
}
slouken@10773
  1019
slouken@10773
  1020
static SDL_bool
slouken@10773
  1021
SetupLibSampleRateResampling(SDL_AudioStream *stream)
slouken@10773
  1022
{
icculus@10790
  1023
    int result = 0;
icculus@10790
  1024
    SRC_STATE *state = NULL;
slouken@10773
  1025
icculus@10790
  1026
    if (SRC_available) {
icculus@10849
  1027
        state = SRC_src_new(SRC_converter, stream->pre_resample_channels, &result);
icculus@10790
  1028
        if (!state) {
icculus@10790
  1029
            SDL_SetError("src_new() failed: %s", SRC_src_strerror(result));
icculus@10790
  1030
        }
slouken@10773
  1031
    }
slouken@10773
  1032
icculus@10790
  1033
    if (!state) {
icculus@10790
  1034
        SDL_CleanupAudioStreamResampler_SRC(stream);
slouken@10773
  1035
        return SDL_FALSE;
slouken@10773
  1036
    }
slouken@10773
  1037
slouken@10773
  1038
    stream->resampler_state = state;
slouken@10773
  1039
    stream->resampler_func = SDL_ResampleAudioStream_SRC;
slouken@10773
  1040
    stream->reset_resampler_func = SDL_ResetAudioStreamResampler_SRC;
slouken@10773
  1041
    stream->cleanup_resampler_func = SDL_CleanupAudioStreamResampler_SRC;
slouken@10773
  1042
slouken@10773
  1043
    return SDL_TRUE;
slouken@10773
  1044
}
icculus@10790
  1045
#endif /* HAVE_LIBSAMPLERATE_H */
slouken@10773
  1046
slouken@10773
  1047
slouken@10773
  1048
typedef struct
slouken@10773
  1049
{
icculus@10757
  1050
    SDL_bool resampler_seeded;
icculus@10842
  1051
    union
icculus@10842
  1052
    {
icculus@10842
  1053
        float f[8];
icculus@10842
  1054
        Sint16 si16[2];
icculus@10842
  1055
    } resampler_state;
slouken@10773
  1056
} SDL_AudioStreamResamplerState;
slouken@10773
  1057
slouken@10773
  1058
static int
icculus@10842
  1059
SDL_ResampleAudioStream(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
slouken@10773
  1060
{
icculus@10842
  1061
    const float *inbuf = (const float *) _inbuf;
icculus@10842
  1062
    float *outbuf = (float *) _outbuf;
slouken@10773
  1063
    SDL_AudioStreamResamplerState *state = (SDL_AudioStreamResamplerState*)stream->resampler_state;
slouken@10773
  1064
    const int chans = (int)stream->pre_resample_channels;
slouken@10773
  1065
icculus@10842
  1066
    SDL_assert(chans <= SDL_arraysize(state->resampler_state.f));
slouken@10773
  1067
slouken@10773
  1068
    if (!state->resampler_seeded) {
icculus@10842
  1069
        SDL_memcpy(state->resampler_state.f, inbuf, chans * sizeof (float));
slouken@10773
  1070
        state->resampler_seeded = SDL_TRUE;
slouken@10773
  1071
    }
slouken@10773
  1072
icculus@10842
  1073
    return SDL_ResampleAudioSimple(chans, stream->rate_incr, state->resampler_state.f, inbuf, inbuflen, outbuf, outbuflen);
icculus@10842
  1074
}
icculus@10842
  1075
icculus@10842
  1076
static int
icculus@10842
  1077
SDL_ResampleAudioStream_si16_c2(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
icculus@10842
  1078
{
icculus@10842
  1079
    const Sint16 *inbuf = (const Sint16 *) _inbuf;
icculus@10842
  1080
    Sint16 *outbuf = (Sint16 *) _outbuf;
icculus@10842
  1081
    SDL_AudioStreamResamplerState *state = (SDL_AudioStreamResamplerState*)stream->resampler_state;
icculus@10842
  1082
icculus@10926
  1083
    SDL_assert(((int)stream->pre_resample_channels) <= SDL_arraysize(state->resampler_state.si16));
icculus@10842
  1084
icculus@10842
  1085
    if (!state->resampler_seeded) {
icculus@10842
  1086
        state->resampler_state.si16[0] = inbuf[0];
icculus@10842
  1087
        state->resampler_state.si16[1] = inbuf[1];
icculus@10842
  1088
        state->resampler_seeded = SDL_TRUE;
icculus@10842
  1089
    }
icculus@10842
  1090
icculus@10842
  1091
    return SDL_ResampleAudioSimple_si16_c2(stream->rate_incr, state->resampler_state.si16, inbuf, inbuflen, outbuf, outbuflen);
slouken@10773
  1092
}
slouken@10773
  1093
slouken@10773
  1094
static void
slouken@10773
  1095
SDL_ResetAudioStreamResampler(SDL_AudioStream *stream)
slouken@10773
  1096
{
slouken@10773
  1097
    SDL_AudioStreamResamplerState *state = (SDL_AudioStreamResamplerState*)stream->resampler_state;
slouken@10773
  1098
    state->resampler_seeded = SDL_FALSE;
slouken@10773
  1099
}
slouken@10773
  1100
slouken@10773
  1101
static void
slouken@10773
  1102
SDL_CleanupAudioStreamResampler(SDL_AudioStream *stream)
slouken@10773
  1103
{
slouken@10773
  1104
    SDL_free(stream->resampler_state);
slouken@10773
  1105
}
icculus@10757
  1106
icculus@10789
  1107
SDL_AudioStream *
icculus@10789
  1108
SDL_NewAudioStream(const SDL_AudioFormat src_format,
icculus@10789
  1109
                   const Uint8 src_channels,
icculus@10789
  1110
                   const int src_rate,
icculus@10789
  1111
                   const SDL_AudioFormat dst_format,
icculus@10789
  1112
                   const Uint8 dst_channels,
icculus@10789
  1113
                   const int dst_rate)
icculus@10757
  1114
{
icculus@10757
  1115
    const int packetlen = 4096;  /* !!! FIXME: good enough for now. */
icculus@10757
  1116
    Uint8 pre_resample_channels;
icculus@10757
  1117
    SDL_AudioStream *retval;
icculus@10842
  1118
#ifndef HAVE_LIBSAMPLERATE_H
icculus@10842
  1119
    const SDL_bool SRC_available = SDL_FALSE;
icculus@10842
  1120
#endif
icculus@10757
  1121
icculus@10757
  1122
    retval = (SDL_AudioStream *) SDL_calloc(1, sizeof (SDL_AudioStream));
icculus@10757
  1123
    if (!retval) {
icculus@10757
  1124
        return NULL;
icculus@10757
  1125
    }
icculus@10757
  1126
icculus@10757
  1127
    /* If increasing channels, do it after resampling, since we'd just
icculus@10757
  1128
       do more work to resample duplicate channels. If we're decreasing, do
icculus@10757
  1129
       it first so we resample the interpolated data instead of interpolating
icculus@10757
  1130
       the resampled data (!!! FIXME: decide if that works in practice, though!). */
icculus@10757
  1131
    pre_resample_channels = SDL_min(src_channels, dst_channels);
icculus@10757
  1132
icculus@10883
  1133
    retval->src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels;
icculus@10757
  1134
    retval->src_format = src_format;
icculus@10757
  1135
    retval->src_channels = src_channels;
icculus@10757
  1136
    retval->src_rate = src_rate;
icculus@10883
  1137
    retval->dst_sample_frame_size = (SDL_AUDIO_BITSIZE(dst_format) / 8) * dst_channels;
icculus@10757
  1138
    retval->dst_format = dst_format;
icculus@10757
  1139
    retval->dst_channels = dst_channels;
icculus@10757
  1140
    retval->dst_rate = dst_rate;
icculus@10757
  1141
    retval->pre_resample_channels = pre_resample_channels;
icculus@10757
  1142
    retval->packetlen = packetlen;
icculus@10757
  1143
    retval->rate_incr = ((double) dst_rate) / ((double) src_rate);
icculus@10757
  1144
icculus@10757
  1145
    /* Not resampling? It's an easy conversion (and maybe not even that!). */
icculus@10757
  1146
    if (src_rate == dst_rate) {
icculus@10757
  1147
        retval->cvt_before_resampling.needed = SDL_FALSE;
slouken@10773
  1148
        if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, src_format, src_channels, dst_rate, dst_format, dst_channels, dst_rate) < 0) {
slouken@10773
  1149
            SDL_FreeAudioStream(retval);
icculus@10757
  1150
            return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */
icculus@10757
  1151
        }
icculus@10842
  1152
    /* fast path special case for stereo Sint16 data that just needs resampling. */
icculus@10842
  1153
    } else if ((!SRC_available) && (src_channels == 2) && (dst_channels == 2) && (src_format == AUDIO_S16SYS) && (dst_format == AUDIO_S16SYS)) {
icculus@10842
  1154
        SDL_assert(src_rate != dst_rate);
icculus@10842
  1155
        retval->resampler_state = SDL_calloc(1, sizeof(SDL_AudioStreamResamplerState));
icculus@10842
  1156
        if (!retval->resampler_state) {
icculus@10842
  1157
            SDL_FreeAudioStream(retval);
icculus@10842
  1158
            SDL_OutOfMemory();
icculus@10842
  1159
            return NULL;
icculus@10842
  1160
        }
icculus@10842
  1161
        retval->resampler_func = SDL_ResampleAudioStream_si16_c2;
icculus@10842
  1162
        retval->reset_resampler_func = SDL_ResetAudioStreamResampler;
icculus@10842
  1163
        retval->cleanup_resampler_func = SDL_CleanupAudioStreamResampler;
icculus@10757
  1164
    } else {
icculus@10757
  1165
        /* Don't resample at first. Just get us to Float32 format. */
icculus@10757
  1166
        /* !!! FIXME: convert to int32 on devices without hardware float. */
slouken@10773
  1167
        if (SDL_BuildAudioCVT(&retval->cvt_before_resampling, src_format, src_channels, src_rate, AUDIO_F32SYS, pre_resample_channels, src_rate) < 0) {
slouken@10773
  1168
            SDL_FreeAudioStream(retval);
icculus@10757
  1169
            return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */
icculus@10757
  1170
        }
icculus@10757
  1171
slouken@10777
  1172
#ifdef HAVE_LIBSAMPLERATE_H
slouken@10773
  1173
        SetupLibSampleRateResampling(retval);
slouken@10773
  1174
#endif
slouken@10773
  1175
slouken@10773
  1176
        if (!retval->resampler_func) {
slouken@10773
  1177
            retval->resampler_state = SDL_calloc(1, sizeof(SDL_AudioStreamResamplerState));
slouken@10773
  1178
            if (!retval->resampler_state) {
slouken@10773
  1179
                SDL_FreeAudioStream(retval);
slouken@10773
  1180
                SDL_OutOfMemory();
slouken@10773
  1181
                return NULL;
slouken@10773
  1182
            }
slouken@10773
  1183
            retval->resampler_func = SDL_ResampleAudioStream;
slouken@10773
  1184
            retval->reset_resampler_func = SDL_ResetAudioStreamResampler;
slouken@10773
  1185
            retval->cleanup_resampler_func = SDL_CleanupAudioStreamResampler;
slouken@10773
  1186
        }
slouken@10773
  1187
icculus@10757
  1188
        /* Convert us to the final format after resampling. */
slouken@10773
  1189
        if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, AUDIO_F32SYS, pre_resample_channels, dst_rate, dst_format, dst_channels, dst_rate) < 0) {
slouken@10773
  1190
            SDL_FreeAudioStream(retval);
icculus@10757
  1191
            return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */
icculus@10757
  1192
        }
icculus@10757
  1193
    }
icculus@10757
  1194
icculus@10757
  1195
    retval->queue = SDL_NewDataQueue(packetlen, packetlen * 2);
icculus@10757
  1196
    if (!retval->queue) {
slouken@10773
  1197
        SDL_FreeAudioStream(retval);
icculus@10757
  1198
        return NULL;  /* SDL_NewDataQueue should have called SDL_SetError. */
icculus@10757
  1199
    }
icculus@10757
  1200
icculus@10757
  1201
    return retval;
icculus@10757
  1202
}
icculus@10757
  1203
icculus@10757
  1204
int
icculus@10757
  1205
SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _buflen)
icculus@10757
  1206
{
icculus@10757
  1207
    int buflen = (int) _buflen;
icculus@10846
  1208
    const void *origbuf = buf;
icculus@10757
  1209
icculus@10844
  1210
    /* !!! FIXME: several converters can take advantage of SIMD, but only
icculus@10844
  1211
       !!! FIXME:  if the data is aligned to 16 bytes. EnsureStreamBufferSize()
icculus@10844
  1212
       !!! FIXME:  guarantees the buffer will align, but the
icculus@10844
  1213
       !!! FIXME:  converters will iterate over the data backwards if
icculus@10844
  1214
       !!! FIXME:  the output grows, and this means we won't align if buflen
icculus@10844
  1215
       !!! FIXME:  isn't a multiple of 16. In these cases, we should chop off
icculus@10844
  1216
       !!! FIXME:  a few samples at the end and convert them separately. */
icculus@10844
  1217
icculus@10757
  1218
    if (!stream) {
icculus@10757
  1219
        return SDL_InvalidParamError("stream");
icculus@10757
  1220
    } else if (!buf) {
icculus@10757
  1221
        return SDL_InvalidParamError("buf");
icculus@10757
  1222
    } else if (buflen == 0) {
icculus@10757
  1223
        return 0;  /* nothing to do. */
icculus@10757
  1224
    } else if ((buflen % stream->src_sample_frame_size) != 0) {
icculus@10757
  1225
        return SDL_SetError("Can't add partial sample frames");
icculus@10757
  1226
    }
icculus@10757
  1227
icculus@10757
  1228
    if (stream->cvt_before_resampling.needed) {
icculus@10757
  1229
        const int workbuflen = buflen * stream->cvt_before_resampling.len_mult;  /* will be "* 1" if not needed */
icculus@10844
  1230
        Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen);
icculus@10757
  1231
        if (workbuf == NULL) {
icculus@10757
  1232
            return -1;  /* probably out of memory. */
icculus@10757
  1233
        }
icculus@10846
  1234
        SDL_assert(buf == origbuf);
icculus@10757
  1235
        SDL_memcpy(workbuf, buf, buflen);
icculus@10757
  1236
        stream->cvt_before_resampling.buf = workbuf;
icculus@10757
  1237
        stream->cvt_before_resampling.len = buflen;
icculus@10757
  1238
        if (SDL_ConvertAudio(&stream->cvt_before_resampling) == -1) {
icculus@10757
  1239
            return -1;   /* uhoh! */
icculus@10757
  1240
        }
icculus@10757
  1241
        buf = workbuf;
icculus@10757
  1242
        buflen = stream->cvt_before_resampling.len_cvt;
icculus@10757
  1243
    }
icculus@10757
  1244
icculus@10757
  1245
    if (stream->dst_rate != stream->src_rate) {
icculus@10757
  1246
        const int workbuflen = buflen * ((int) SDL_ceil(stream->rate_incr));
icculus@10844
  1247
        Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen);
icculus@10757
  1248
        if (workbuf == NULL) {
icculus@10757
  1249
            return -1;  /* probably out of memory. */
icculus@10757
  1250
        }
icculus@10851
  1251
        /* don't SDL_memcpy(workbuf, buf, buflen) here; our resampler can work inplace or not,
icculus@10851
  1252
           libsamplerate needs buffers to be separate; either way, avoid a copy here if possible. */
icculus@10851
  1253
        if (buf != origbuf) {
icculus@10851
  1254
            buf = workbuf;  /* in case we realloc()'d the pointer. */
icculus@10843
  1255
        }
icculus@10851
  1256
        buflen = stream->resampler_func(stream, buf, buflen, workbuf, workbuflen);
icculus@10851
  1257
        buf = EnsureStreamBufferSize(stream, workbuflen);
icculus@10851
  1258
        SDL_assert(buf != NULL);  /* shouldn't be growing, just aligning. */
icculus@10757
  1259
    }
icculus@10757
  1260
icculus@10757
  1261
    if (stream->cvt_after_resampling.needed) {
icculus@10842
  1262
        const int workbuflen = buflen * stream->cvt_after_resampling.len_mult;  /* will be "* 1" if not needed */
icculus@10844
  1263
        Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen);
icculus@10757
  1264
        if (workbuf == NULL) {
icculus@10757
  1265
            return -1;  /* probably out of memory. */
icculus@10757
  1266
        }
icculus@10846
  1267
        if (buf == origbuf) {  /* copy if we haven't before. */
icculus@10843
  1268
            SDL_memcpy(workbuf, buf, buflen);
icculus@10843
  1269
        }
icculus@10757
  1270
        stream->cvt_after_resampling.buf = workbuf;
icculus@10757
  1271
        stream->cvt_after_resampling.len = buflen;
icculus@10757
  1272
        if (SDL_ConvertAudio(&stream->cvt_after_resampling) == -1) {
icculus@10757
  1273
            return -1;   /* uhoh! */
icculus@10757
  1274
        }
icculus@10757
  1275
        buf = workbuf;
icculus@10757
  1276
        buflen = stream->cvt_after_resampling.len_cvt;
icculus@10757
  1277
    }
icculus@10757
  1278
icculus@10757
  1279
    return SDL_WriteToDataQueue(stream->queue, buf, buflen);
icculus@10757
  1280
}
icculus@10757
  1281
icculus@10757
  1282
void
icculus@10757
  1283
SDL_AudioStreamClear(SDL_AudioStream *stream)
icculus@10757
  1284
{
icculus@10757
  1285
    if (!stream) {
icculus@10757
  1286
        SDL_InvalidParamError("stream");
icculus@10757
  1287
    } else {
icculus@10757
  1288
        SDL_ClearDataQueue(stream->queue, stream->packetlen * 2);
icculus@10776
  1289
        if (stream->reset_resampler_func) {
icculus@10776
  1290
            stream->reset_resampler_func(stream);
icculus@10776
  1291
        }
icculus@10757
  1292
    }
icculus@10757
  1293
}
icculus@10757
  1294
icculus@10757
  1295
icculus@10757
  1296
/* get converted/resampled data from the stream */
icculus@10757
  1297
int
icculus@10764
  1298
SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, const Uint32 len)
icculus@10757
  1299
{
icculus@10757
  1300
    if (!stream) {
icculus@10757
  1301
        return SDL_InvalidParamError("stream");
icculus@10757
  1302
    } else if (!buf) {
icculus@10757
  1303
        return SDL_InvalidParamError("buf");
icculus@10757
  1304
    } else if (len == 0) {
icculus@10757
  1305
        return 0;  /* nothing to do. */
icculus@10757
  1306
    } else if ((len % stream->dst_sample_frame_size) != 0) {
icculus@10757
  1307
        return SDL_SetError("Can't request partial sample frames");
icculus@10757
  1308
    }
icculus@10757
  1309
icculus@10764
  1310
    return (int) SDL_ReadFromDataQueue(stream->queue, buf, len);
icculus@10757
  1311
}
icculus@10757
  1312
icculus@10757
  1313
/* number of converted/resampled bytes available */
icculus@10757
  1314
int
icculus@10757
  1315
SDL_AudioStreamAvailable(SDL_AudioStream *stream)
icculus@10757
  1316
{
icculus@10757
  1317
    return stream ? (int) SDL_CountDataQueue(stream->queue) : 0;
icculus@10757
  1318
}
icculus@10757
  1319
icculus@10757
  1320
/* dispose of a stream */
icculus@10757
  1321
void
icculus@10757
  1322
SDL_FreeAudioStream(SDL_AudioStream *stream)
icculus@10757
  1323
{
icculus@10757
  1324
    if (stream) {
slouken@10773
  1325
        if (stream->cleanup_resampler_func) {
slouken@10773
  1326
            stream->cleanup_resampler_func(stream);
slouken@10773
  1327
        }
icculus@10757
  1328
        SDL_FreeDataQueue(stream->queue);
icculus@10844
  1329
        SDL_free(stream->work_buffer_base);
icculus@10757
  1330
        SDL_free(stream);
icculus@10757
  1331
    }
icculus@10757
  1332
}
icculus@10757
  1333
icculus@10575
  1334
/* vi: set ts=4 sw=4 expandtab: */
slouken@2716
  1335