audio: A whole bunch of improvements to audio conversion (thanks, Solra!).
authorRyan C. Gordon <icculus@icculus.org>
Tue, 29 Aug 2017 00:41:45 -0400
changeset 114054e12f78c2b0e
parent 11404 bd5b569b2a1b
child 11406 f40c2dedaded
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.
src/audio/SDL_audiocvt.c
     1.1 --- a/src/audio/SDL_audiocvt.c	Tue Aug 29 00:36:17 2017 -0400
     1.2 +++ b/src/audio/SDL_audiocvt.c	Tue Aug 29 00:41:45 2017 -0400
     1.3 @@ -36,7 +36,7 @@
     1.4  #endif
     1.5  
     1.6  #if HAVE_SSE3_INTRINSICS
     1.7 -/* Effectively mix right and left channels into a single channel */
     1.8 +/* Convert from stereo to mono. Average left and right. */
     1.9  static void SDLCALL
    1.10  SDL_ConvertStereoToMono_SSE3(SDL_AudioCVT * cvt, SDL_AudioFormat format)
    1.11  {
    1.12 @@ -71,7 +71,7 @@
    1.13  }
    1.14  #endif
    1.15  
    1.16 -/* Effectively mix right and left channels into a single channel */
    1.17 +/* Convert from stereo to mono. Average left and right. */
    1.18  static void SDLCALL
    1.19  SDL_ConvertStereoToMono(SDL_AudioCVT * cvt, SDL_AudioFormat format)
    1.20  {
    1.21 @@ -93,7 +93,7 @@
    1.22  }
    1.23  
    1.24  
    1.25 -/* Convert from 5.1 to stereo. Average left and right, discard subwoofer. */
    1.26 +/* Convert from 5.1 to stereo. Average left and right, distribute center, discard LFE. */
    1.27  static void SDLCALL
    1.28  SDL_Convert51ToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
    1.29  {
    1.30 @@ -104,11 +104,11 @@
    1.31      LOG_DEBUG_CONVERT("5.1", "stereo");
    1.32      SDL_assert(format == AUDIO_F32SYS);
    1.33  
    1.34 -    /* this assumes FL+FR+FC+subwoof+BL+BR layout. */
    1.35 +    /* SDL's 5.1 layout: FL+FR+FC+LFE+BL+BR */
    1.36      for (i = cvt->len_cvt / (sizeof (float) * 6); i; --i, src += 6, dst += 2) {
    1.37 -        const double front_center = (double) src[2];
    1.38 -        dst[0] = (float) ((src[0] + front_center + src[4]) / 3.0);  /* left */
    1.39 -        dst[1] = (float) ((src[1] + front_center + src[5]) / 3.0);  /* right */
    1.40 +        const float front_center_distributed = src[2] * 0.5f;
    1.41 +        dst[0] = (src[0] + front_center_distributed + src[4]) / 2.5f;  /* left */
    1.42 +        dst[1] = (src[1] + front_center_distributed + src[5]) / 2.5f;  /* right */
    1.43      }
    1.44  
    1.45      cvt->len_cvt /= 3;
    1.46 @@ -118,7 +118,60 @@
    1.47  }
    1.48  
    1.49  
    1.50 -/* Convert from 5.1 to quad */
    1.51 +/* Convert from quad to stereo. Average left and right. */
    1.52 +static void SDLCALL
    1.53 +SDL_ConvertQuadToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
    1.54 +{
    1.55 +    float *dst = (float *) cvt->buf;
    1.56 +    const float *src = dst;
    1.57 +    int i;
    1.58 +
    1.59 +    LOG_DEBUG_CONVERT("quad", "stereo");
    1.60 +    SDL_assert(format == AUDIO_F32SYS);
    1.61 +
    1.62 +    for (i = cvt->len_cvt / (sizeof (float) * 4); i; --i, src += 4, dst += 2) {
    1.63 +        dst[0] = (src[0] + src[2]) * 0.5f; /* left */
    1.64 +        dst[1] = (src[1] + src[3]) * 0.5f; /* right */
    1.65 +    }
    1.66 +
    1.67 +    cvt->len_cvt /= 3;
    1.68 +    if (cvt->filters[++cvt->filter_index]) {
    1.69 +        cvt->filters[cvt->filter_index] (cvt, format);
    1.70 +    }
    1.71 +}
    1.72 +
    1.73 +
    1.74 +/* Convert from 7.1 to 5.1. Distribute sides across front and back. */
    1.75 +static void SDLCALL
    1.76 +SDL_Convert71To51(SDL_AudioCVT * cvt, SDL_AudioFormat format)
    1.77 +{
    1.78 +    float *dst = (float *) cvt->buf;
    1.79 +    const float *src = dst;
    1.80 +    int i;
    1.81 +
    1.82 +    LOG_DEBUG_CONVERT("7.1", "5.1");
    1.83 +    SDL_assert(format == AUDIO_F32SYS);
    1.84 +
    1.85 +    for (i = cvt->len_cvt / (sizeof (float) * 8); i; --i, src += 8, dst += 6) {
    1.86 +        const float surround_left_distributed = src[6] * 0.5f;
    1.87 +        const float surround_right_distributed = src[7] * 0.5f;
    1.88 +        dst[0] = (src[0] + surround_left_distributed) / 1.5f;  /* FL */
    1.89 +        dst[1] = (src[1] + surround_right_distributed) / 1.5f;  /* FR */
    1.90 +        dst[2] = src[2] / 1.5f; /* CC */
    1.91 +        dst[3] = src[3] / 1.5f; /* LFE */
    1.92 +        dst[4] = (src[4] + surround_left_distributed) / 1.5f;  /* BL */
    1.93 +        dst[5] = (src[5] + surround_right_distributed) / 1.5f;  /* BR */
    1.94 +    }
    1.95 +
    1.96 +    cvt->len_cvt /= 8;
    1.97 +    cvt->len_cvt *= 6;
    1.98 +    if (cvt->filters[++cvt->filter_index]) {
    1.99 +        cvt->filters[cvt->filter_index] (cvt, format);
   1.100 +    }
   1.101 +}
   1.102 +
   1.103 +
   1.104 +/* Convert from 5.1 to quad. Distribute center across front, discard LFE. */
   1.105  static void SDLCALL
   1.106  SDL_Convert51ToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
   1.107  {
   1.108 @@ -129,14 +182,14 @@
   1.109      LOG_DEBUG_CONVERT("5.1", "quad");
   1.110      SDL_assert(format == AUDIO_F32SYS);
   1.111  
   1.112 -    /* assumes quad is FL+FR+BL+BR layout and 5.1 is FL+FR+FC+subwoof+BL+BR */
   1.113 +    /* SDL's 4.0 layout: FL+FR+BL+BR */
   1.114 +    /* SDL's 5.1 layout: FL+FR+FC+LFE+BL+BR */
   1.115      for (i = cvt->len_cvt / (sizeof (float) * 6); i; --i, src += 6, dst += 4) {
   1.116 -        /* FIXME: this is a good candidate for SIMD. */
   1.117 -        const double front_center = (double) src[2];
   1.118 -        dst[0] = (float) ((src[0] + front_center) * 0.5);  /* FL */
   1.119 -        dst[1] = (float) ((src[1] + front_center) * 0.5);  /* FR */
   1.120 -        dst[2] = (float) ((src[4] + front_center) * 0.5);  /* BL */
   1.121 -        dst[3] = (float) ((src[5] + front_center) * 0.5);  /* BR */
   1.122 +        const float front_center_distributed = src[2] * 0.5f;
   1.123 +        dst[0] = (src[0] + front_center_distributed) / 1.5f;  /* FL */
   1.124 +        dst[1] = (src[1] + front_center_distributed) / 1.5f;  /* FR */
   1.125 +        dst[2] = src[4] / 1.5f;  /* BL */
   1.126 +        dst[3] = src[5] / 1.5f;  /* BR */
   1.127      }
   1.128  
   1.129      cvt->len_cvt /= 6;
   1.130 @@ -147,7 +200,7 @@
   1.131  }
   1.132  
   1.133  
   1.134 -/* Duplicate a mono channel to both stereo channels */
   1.135 +/* Upmix mono to stereo (by duplication) */
   1.136  static void SDLCALL
   1.137  SDL_ConvertMonoToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
   1.138  {
   1.139 @@ -171,7 +224,7 @@
   1.140  }
   1.141  
   1.142  
   1.143 -/* Duplicate a stereo channel to a pseudo-5.1 stream */
   1.144 +/* Upmix stereo to a pseudo-5.1 stream */
   1.145  static void SDLCALL
   1.146  SDL_ConvertStereoTo51(SDL_AudioCVT * cvt, SDL_AudioFormat format)
   1.147  {
   1.148 @@ -183,16 +236,17 @@
   1.149      LOG_DEBUG_CONVERT("stereo", "5.1");
   1.150      SDL_assert(format == AUDIO_F32SYS);
   1.151  
   1.152 -    for (i = cvt->len_cvt / 8; i; --i) {
   1.153 +    for (i = cvt->len_cvt / (sizeof(float) * 2); i; --i) {
   1.154          dst -= 6;
   1.155          src -= 2;
   1.156          lf = src[0];
   1.157          rf = src[1];
   1.158          ce = (lf + rf) * 0.5f;
   1.159 +        /* !!! FIXME: FL and FR may clip */
   1.160          dst[0] = lf + (lf - ce);  /* FL */
   1.161          dst[1] = rf + (rf - ce);  /* FR */
   1.162          dst[2] = ce;  /* FC */
   1.163 -        dst[3] = ce;  /* !!! FIXME: wrong! This is the subwoofer. */
   1.164 +        dst[3] = 0;   /* LFE (only meant for special LFE effects) */
   1.165          dst[4] = lf;  /* BL */
   1.166          dst[5] = rf;  /* BR */
   1.167      }
   1.168 @@ -204,7 +258,44 @@
   1.169  }
   1.170  
   1.171  
   1.172 -/* Duplicate a stereo channel to a pseudo-4.0 stream */
   1.173 +/* Upmix quad to a pseudo-5.1 stream */
   1.174 +static void SDLCALL
   1.175 +SDL_ConvertQuadTo51(SDL_AudioCVT * cvt, SDL_AudioFormat format)
   1.176 +{
   1.177 +    int i;
   1.178 +    float lf, rf, lb, rb, ce;
   1.179 +    const float *src = (const float *) (cvt->buf + cvt->len_cvt);
   1.180 +    float *dst = (float *) (cvt->buf + cvt->len_cvt * 3 / 2);
   1.181 +
   1.182 +    LOG_DEBUG_CONVERT("quad", "5.1");
   1.183 +    SDL_assert(format == AUDIO_F32SYS);
   1.184 +    SDL_assert(cvt->len_cvt % (sizeof(float) * 4) == 0);
   1.185 +
   1.186 +    for (i = cvt->len_cvt / (sizeof(float) * 4); i; --i) {
   1.187 +        dst -= 6;
   1.188 +        src -= 4;
   1.189 +        lf = src[0];
   1.190 +        rf = src[1];
   1.191 +        lb = src[2];
   1.192 +        rb = src[3];
   1.193 +        ce = (lf + rf) * 0.5f;
   1.194 +        /* !!! FIXME: FL and FR may clip */
   1.195 +        dst[0] = lf + (lf - ce);  /* FL */
   1.196 +        dst[1] = rf + (rf - ce);  /* FR */
   1.197 +        dst[2] = ce;  /* FC */
   1.198 +        dst[3] = 0;   /* LFE (only meant for special LFE effects) */
   1.199 +        dst[4] = lb;  /* BL */
   1.200 +        dst[5] = rb;  /* BR */
   1.201 +    }
   1.202 +
   1.203 +    cvt->len_cvt = cvt->len_cvt * 3 / 2;
   1.204 +    if (cvt->filters[++cvt->filter_index]) {
   1.205 +        cvt->filters[cvt->filter_index] (cvt, format);
   1.206 +    }
   1.207 +}
   1.208 +
   1.209 +
   1.210 +/* Upmix stereo to a pseudo-4.0 stream (by duplication) */
   1.211  static void SDLCALL
   1.212  SDL_ConvertStereoToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
   1.213  {
   1.214 @@ -216,7 +307,7 @@
   1.215      LOG_DEBUG_CONVERT("stereo", "quad");
   1.216      SDL_assert(format == AUDIO_F32SYS);
   1.217  
   1.218 -    for (i = cvt->len_cvt / 8; i; --i) {
   1.219 +    for (i = cvt->len_cvt / (sizeof(float) * 2); i; --i) {
   1.220          dst -= 4;
   1.221          src -= 2;
   1.222          lf = src[0];
   1.223 @@ -233,6 +324,52 @@
   1.224      }
   1.225  }
   1.226  
   1.227 +
   1.228 +/* Upmix 5.1 to 7.1 */
   1.229 +static void SDLCALL
   1.230 +SDL_Convert51To71(SDL_AudioCVT * cvt, SDL_AudioFormat format)
   1.231 +{
   1.232 +    float lf, rf, lb, rb, ls, rs;
   1.233 +    int i;
   1.234 +    const float *src = (const float *) (cvt->buf + cvt->len_cvt);
   1.235 +    float *dst = (float *) (cvt->buf + cvt->len_cvt * 4 / 3);
   1.236 +
   1.237 +    LOG_DEBUG_CONVERT("5.1", "7.1");
   1.238 +    SDL_assert(format == AUDIO_F32SYS);
   1.239 +    SDL_assert(cvt->len_cvt % (sizeof(float) * 6) == 0);
   1.240 +
   1.241 +    for (i = cvt->len_cvt / (sizeof(float) * 6); i; --i) {
   1.242 +        dst -= 8;
   1.243 +        src -= 6;
   1.244 +        lf = src[0];
   1.245 +        rf = src[1];
   1.246 +        lb = src[4];
   1.247 +        rb = src[5];
   1.248 +        ls = (lf + lb) * 0.5f;
   1.249 +        rs = (rf + rb) * 0.5f;
   1.250 +        /* !!! FIXME: these four may clip */
   1.251 +        lf += lf - ls;
   1.252 +        rf += rf - ls;
   1.253 +        lb += lb - ls;
   1.254 +        rb += rb - ls;
   1.255 +        dst[3] = src[3];  /* LFE */
   1.256 +        dst[2] = src[2];  /* FC */
   1.257 +        dst[7] = rs; /* SR */
   1.258 +        dst[6] = ls; /* SL */
   1.259 +        dst[5] = rb;  /* BR */
   1.260 +        dst[4] = lb;  /* BL */
   1.261 +        dst[1] = rf;  /* FR */
   1.262 +        dst[0] = lf;  /* FL */
   1.263 +    }
   1.264 +
   1.265 +    cvt->len_cvt = cvt->len_cvt * 4 / 3;
   1.266 +
   1.267 +    if (cvt->filters[++cvt->filter_index]) {
   1.268 +        cvt->filters[cvt->filter_index] (cvt, format);
   1.269 +    }
   1.270 +}
   1.271 +
   1.272 +
   1.273  static int
   1.274  SDL_ResampleAudioSimple(const int chans, const double rate_incr,
   1.275                          float *last_sample, const float *inbuf,
   1.276 @@ -732,9 +869,9 @@
   1.277          case 2:  /* stereo */
   1.278          case 4:  /* quad */
   1.279          case 6:  /* 5.1 */
   1.280 -            return SDL_TRUE;  /* supported. */
   1.281 +        case 8:  /* 7.1 */
   1.282 +          return SDL_TRUE;  /* supported. */
   1.283  
   1.284 -        case 8:  /* !!! FIXME: 7.1 */
   1.285          default:
   1.286              break;
   1.287      }
   1.288 @@ -857,7 +994,9 @@
   1.289      }
   1.290  
   1.291      /* Channel conversion */
   1.292 -    if (src_channels != dst_channels) {
   1.293 +    if (src_channels < dst_channels) {
   1.294 +        /* Upmixing */
   1.295 +        /* Mono -> Stereo [-> ...] */
   1.296          if ((src_channels == 1) && (dst_channels > 1)) {
   1.297              if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertMonoToStereo) < 0) {
   1.298                  return -1;
   1.299 @@ -866,7 +1005,8 @@
   1.300              src_channels = 2;
   1.301              cvt->len_ratio *= 2;
   1.302          }
   1.303 -        if ((src_channels == 2) && (dst_channels == 6)) {
   1.304 +        /* [Mono ->] Stereo -> 5.1 [-> 7.1] */
   1.305 +        if ((src_channels == 2) && (dst_channels >= 6)) {
   1.306              if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertStereoTo51) < 0) {
   1.307                  return -1;
   1.308              }
   1.309 @@ -874,6 +1014,27 @@
   1.310              cvt->len_mult *= 3;
   1.311              cvt->len_ratio *= 3;
   1.312          }
   1.313 +        /* Quad -> 5.1 [-> 7.1] */
   1.314 +        if ((src_channels == 4) && (dst_channels >= 6)) {
   1.315 +            if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertQuadTo51) < 0) {
   1.316 +                return -1;
   1.317 +            }
   1.318 +            src_channels = 6;
   1.319 +            cvt->len_mult = (cvt->len_mult * 3 + 1) / 2;
   1.320 +            cvt->len_ratio *= 1.5;
   1.321 +        }
   1.322 +        /* [[Mono ->] Stereo ->] 5.1 -> 7.1 */
   1.323 +        if ((src_channels == 6) && (dst_channels == 8)) {
   1.324 +            if (SDL_AddAudioCVTFilter(cvt, SDL_Convert51To71) < 0) {
   1.325 +                return -1;
   1.326 +            }
   1.327 +            src_channels = 8;
   1.328 +            cvt->len_mult = (cvt->len_mult * 4 + 2) / 3;
   1.329 +            /* Should be numerically exact with every valid input to this
   1.330 +               function */
   1.331 +            cvt->len_ratio = cvt->len_ratio * 4 / 3;
   1.332 +        }
   1.333 +        /* [Mono ->] Stereo -> Quad */
   1.334          if ((src_channels == 2) && (dst_channels == 4)) {
   1.335              if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertStereoToQuad) < 0) {
   1.336                  return -1;
   1.337 @@ -882,14 +1043,18 @@
   1.338              cvt->len_mult *= 2;
   1.339              cvt->len_ratio *= 2;
   1.340          }
   1.341 -        while ((src_channels * 2) <= dst_channels) {
   1.342 -            if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertMonoToStereo) < 0) {
   1.343 +    } else if (src_channels > dst_channels) {
   1.344 +        /* Downmixing */
   1.345 +        /* 7.1 -> 5.1 [-> Stereo [-> Mono]] */
   1.346 +        /* 7.1 -> 5.1 [-> Quad] */
   1.347 +        if ((src_channels == 8) && (dst_channels <= 6)) {
   1.348 +            if (SDL_AddAudioCVTFilter(cvt, SDL_Convert71To51) < 0) {
   1.349                  return -1;
   1.350              }
   1.351 -            cvt->len_mult *= 2;
   1.352 -            src_channels *= 2;
   1.353 -            cvt->len_ratio *= 2;
   1.354 +            src_channels = 6;
   1.355 +            cvt->len_ratio *= 0.75;
   1.356          }
   1.357 +        /* [7.1 ->] 5.1 -> Stereo [-> Mono] */
   1.358          if ((src_channels == 6) && (dst_channels <= 2)) {
   1.359              if (SDL_AddAudioCVTFilter(cvt, SDL_Convert51ToStereo) < 0) {
   1.360                  return -1;
   1.361 @@ -897,19 +1062,24 @@
   1.362              src_channels = 2;
   1.363              cvt->len_ratio /= 3;
   1.364          }
   1.365 +        /* 5.1 -> Quad */
   1.366          if ((src_channels == 6) && (dst_channels == 4)) {
   1.367              if (SDL_AddAudioCVTFilter(cvt, SDL_Convert51ToQuad) < 0) {
   1.368                  return -1;
   1.369              }
   1.370              src_channels = 4;
   1.371 +            cvt->len_ratio = cvt->len_ratio * 2 / 3;
   1.372 +        }
   1.373 +        /* Quad -> Stereo [-> Mono] */
   1.374 +        if ((src_channels == 4) && (dst_channels <= 2)) {
   1.375 +            if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertQuadToStereo) < 0) {
   1.376 +                return -1;
   1.377 +            }
   1.378 +            src_channels = 2;
   1.379              cvt->len_ratio /= 2;
   1.380          }
   1.381 -        /* This assumes that 4 channel audio is in the format:
   1.382 -           Left {front/back} + Right {front/back}
   1.383 -           so converting to L/R stereo works properly.
   1.384 -         */
   1.385 -        while (((src_channels % 2) == 0) &&
   1.386 -               ((src_channels / 2) >= dst_channels)) {
   1.387 +        /* [... ->] Stereo -> Mono */
   1.388 +        if ((src_channels == 2) && (dst_channels == 1)) {
   1.389              SDL_AudioFilter filter = NULL;
   1.390  
   1.391              #if HAVE_SSE3_INTRINSICS
   1.392 @@ -926,14 +1096,17 @@
   1.393                  return -1;
   1.394              }
   1.395  
   1.396 -            src_channels /= 2;
   1.397 +            src_channels = 1;
   1.398              cvt->len_ratio /= 2;
   1.399          }
   1.400 -        if (src_channels != dst_channels) {
   1.401 -            /* Uh oh.. */ ;
   1.402 -        }
   1.403      }
   1.404  
   1.405 +    if (src_channels != dst_channels) {
   1.406 +        /* All combinations of supported channel counts should have been
   1.407 +           handled by now, but let's be defensive */
   1.408 +      return SDL_SetError("Invalid channel combination");
   1.409 +    }
   1.410 +    
   1.411      /* Do rate conversion, if necessary. Updates (cvt). */
   1.412      if (SDL_BuildAudioResampleCVT(cvt, dst_channels, src_rate, dst_rate) < 0) {
   1.413          return -1;              /* shouldn't happen, but just in case... */