src/audio/SDL_audiocvt.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 29 Aug 2017 00:41:45 -0400
changeset 11405 4e12f78c2b0e
parent 11319 86b1fde471c6
child 11406 f40c2dedaded
permissions -rw-r--r--
audio: A whole bunch of improvements to audio conversion (thanks, Solra!).

"Major changes, roughly in order of appearance:

- Use float math everywhere, instead of promoting to double and casting back
all the time.
- Conserve sound energy when downmixing any channel into two other channels.
- Add a QuadToStereo filter. (The previous technique of reusing StereoToMono
never worked, since it assumed an incorrect channel layout for 4.0.)
- Add a 71to51 filter. This removes just under half of the cases the previous
code would silently break in.
- Add a QuadTo51 filter. More silent breakage fixed.
- Add a 51to71 filter, removing another almost-half of the silently broken
cases.
- Add 8 to the list of values SDL_SupportedChannelCount will accept.
- Change SDL_BuildAudioCVT's channel-related logic to handle every case, and
to actually fail if it fails instead of silently corrupting sound data and/or
crashing down the road."

(Note that SDL doesn't otherwise support 7.1 audio yet, but hopefully it will
soon and the 7.1 converters are an important piece of that. --ryan.)

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