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.
parent
620f5342b5
commit
b128e8802d
|
@ -36,7 +36,7 @@
|
|||
#endif
|
||||
|
||||
#if HAVE_SSE3_INTRINSICS
|
||||
/* Effectively mix right and left channels into a single channel */
|
||||
/* Convert from stereo to mono. Average left and right. */
|
||||
static void SDLCALL
|
||||
SDL_ConvertStereoToMono_SSE3(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
|
@ -71,7 +71,7 @@ SDL_ConvertStereoToMono_SSE3(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
}
|
||||
#endif
|
||||
|
||||
/* Effectively mix right and left channels into a single channel */
|
||||
/* Convert from stereo to mono. Average left and right. */
|
||||
static void SDLCALL
|
||||
SDL_ConvertStereoToMono(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
|
@ -93,7 +93,7 @@ SDL_ConvertStereoToMono(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
}
|
||||
|
||||
|
||||
/* Convert from 5.1 to stereo. Average left and right, discard subwoofer. */
|
||||
/* Convert from 5.1 to stereo. Average left and right, distribute center, discard LFE. */
|
||||
static void SDLCALL
|
||||
SDL_Convert51ToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
|
@ -104,11 +104,11 @@ SDL_Convert51ToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
LOG_DEBUG_CONVERT("5.1", "stereo");
|
||||
SDL_assert(format == AUDIO_F32SYS);
|
||||
|
||||
/* this assumes FL+FR+FC+subwoof+BL+BR layout. */
|
||||
/* SDL's 5.1 layout: FL+FR+FC+LFE+BL+BR */
|
||||
for (i = cvt->len_cvt / (sizeof (float) * 6); i; --i, src += 6, dst += 2) {
|
||||
const double front_center = (double) src[2];
|
||||
dst[0] = (float) ((src[0] + front_center + src[4]) / 3.0); /* left */
|
||||
dst[1] = (float) ((src[1] + front_center + src[5]) / 3.0); /* right */
|
||||
const float front_center_distributed = src[2] * 0.5f;
|
||||
dst[0] = (src[0] + front_center_distributed + src[4]) / 2.5f; /* left */
|
||||
dst[1] = (src[1] + front_center_distributed + src[5]) / 2.5f; /* right */
|
||||
}
|
||||
|
||||
cvt->len_cvt /= 3;
|
||||
|
@ -118,7 +118,60 @@ SDL_Convert51ToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
}
|
||||
|
||||
|
||||
/* Convert from 5.1 to quad */
|
||||
/* Convert from quad to stereo. Average left and right. */
|
||||
static void SDLCALL
|
||||
SDL_ConvertQuadToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
float *dst = (float *) cvt->buf;
|
||||
const float *src = dst;
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_CONVERT("quad", "stereo");
|
||||
SDL_assert(format == AUDIO_F32SYS);
|
||||
|
||||
for (i = cvt->len_cvt / (sizeof (float) * 4); i; --i, src += 4, dst += 2) {
|
||||
dst[0] = (src[0] + src[2]) * 0.5f; /* left */
|
||||
dst[1] = (src[1] + src[3]) * 0.5f; /* right */
|
||||
}
|
||||
|
||||
cvt->len_cvt /= 3;
|
||||
if (cvt->filters[++cvt->filter_index]) {
|
||||
cvt->filters[cvt->filter_index] (cvt, format);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Convert from 7.1 to 5.1. Distribute sides across front and back. */
|
||||
static void SDLCALL
|
||||
SDL_Convert71To51(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
float *dst = (float *) cvt->buf;
|
||||
const float *src = dst;
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_CONVERT("7.1", "5.1");
|
||||
SDL_assert(format == AUDIO_F32SYS);
|
||||
|
||||
for (i = cvt->len_cvt / (sizeof (float) * 8); i; --i, src += 8, dst += 6) {
|
||||
const float surround_left_distributed = src[6] * 0.5f;
|
||||
const float surround_right_distributed = src[7] * 0.5f;
|
||||
dst[0] = (src[0] + surround_left_distributed) / 1.5f; /* FL */
|
||||
dst[1] = (src[1] + surround_right_distributed) / 1.5f; /* FR */
|
||||
dst[2] = src[2] / 1.5f; /* CC */
|
||||
dst[3] = src[3] / 1.5f; /* LFE */
|
||||
dst[4] = (src[4] + surround_left_distributed) / 1.5f; /* BL */
|
||||
dst[5] = (src[5] + surround_right_distributed) / 1.5f; /* BR */
|
||||
}
|
||||
|
||||
cvt->len_cvt /= 8;
|
||||
cvt->len_cvt *= 6;
|
||||
if (cvt->filters[++cvt->filter_index]) {
|
||||
cvt->filters[cvt->filter_index] (cvt, format);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Convert from 5.1 to quad. Distribute center across front, discard LFE. */
|
||||
static void SDLCALL
|
||||
SDL_Convert51ToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
|
@ -129,14 +182,14 @@ SDL_Convert51ToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
LOG_DEBUG_CONVERT("5.1", "quad");
|
||||
SDL_assert(format == AUDIO_F32SYS);
|
||||
|
||||
/* assumes quad is FL+FR+BL+BR layout and 5.1 is FL+FR+FC+subwoof+BL+BR */
|
||||
/* SDL's 4.0 layout: FL+FR+BL+BR */
|
||||
/* SDL's 5.1 layout: FL+FR+FC+LFE+BL+BR */
|
||||
for (i = cvt->len_cvt / (sizeof (float) * 6); i; --i, src += 6, dst += 4) {
|
||||
/* FIXME: this is a good candidate for SIMD. */
|
||||
const double front_center = (double) src[2];
|
||||
dst[0] = (float) ((src[0] + front_center) * 0.5); /* FL */
|
||||
dst[1] = (float) ((src[1] + front_center) * 0.5); /* FR */
|
||||
dst[2] = (float) ((src[4] + front_center) * 0.5); /* BL */
|
||||
dst[3] = (float) ((src[5] + front_center) * 0.5); /* BR */
|
||||
const float front_center_distributed = src[2] * 0.5f;
|
||||
dst[0] = (src[0] + front_center_distributed) / 1.5f; /* FL */
|
||||
dst[1] = (src[1] + front_center_distributed) / 1.5f; /* FR */
|
||||
dst[2] = src[4] / 1.5f; /* BL */
|
||||
dst[3] = src[5] / 1.5f; /* BR */
|
||||
}
|
||||
|
||||
cvt->len_cvt /= 6;
|
||||
|
@ -147,7 +200,7 @@ SDL_Convert51ToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
}
|
||||
|
||||
|
||||
/* Duplicate a mono channel to both stereo channels */
|
||||
/* Upmix mono to stereo (by duplication) */
|
||||
static void SDLCALL
|
||||
SDL_ConvertMonoToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
|
@ -171,7 +224,7 @@ SDL_ConvertMonoToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
}
|
||||
|
||||
|
||||
/* Duplicate a stereo channel to a pseudo-5.1 stream */
|
||||
/* Upmix stereo to a pseudo-5.1 stream */
|
||||
static void SDLCALL
|
||||
SDL_ConvertStereoTo51(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
|
@ -183,16 +236,17 @@ SDL_ConvertStereoTo51(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
LOG_DEBUG_CONVERT("stereo", "5.1");
|
||||
SDL_assert(format == AUDIO_F32SYS);
|
||||
|
||||
for (i = cvt->len_cvt / 8; i; --i) {
|
||||
for (i = cvt->len_cvt / (sizeof(float) * 2); i; --i) {
|
||||
dst -= 6;
|
||||
src -= 2;
|
||||
lf = src[0];
|
||||
rf = src[1];
|
||||
ce = (lf + rf) * 0.5f;
|
||||
/* !!! FIXME: FL and FR may clip */
|
||||
dst[0] = lf + (lf - ce); /* FL */
|
||||
dst[1] = rf + (rf - ce); /* FR */
|
||||
dst[2] = ce; /* FC */
|
||||
dst[3] = ce; /* !!! FIXME: wrong! This is the subwoofer. */
|
||||
dst[3] = 0; /* LFE (only meant for special LFE effects) */
|
||||
dst[4] = lf; /* BL */
|
||||
dst[5] = rf; /* BR */
|
||||
}
|
||||
|
@ -204,7 +258,44 @@ SDL_ConvertStereoTo51(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
}
|
||||
|
||||
|
||||
/* Duplicate a stereo channel to a pseudo-4.0 stream */
|
||||
/* Upmix quad to a pseudo-5.1 stream */
|
||||
static void SDLCALL
|
||||
SDL_ConvertQuadTo51(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
int i;
|
||||
float lf, rf, lb, rb, ce;
|
||||
const float *src = (const float *) (cvt->buf + cvt->len_cvt);
|
||||
float *dst = (float *) (cvt->buf + cvt->len_cvt * 3 / 2);
|
||||
|
||||
LOG_DEBUG_CONVERT("quad", "5.1");
|
||||
SDL_assert(format == AUDIO_F32SYS);
|
||||
SDL_assert(cvt->len_cvt % (sizeof(float) * 4) == 0);
|
||||
|
||||
for (i = cvt->len_cvt / (sizeof(float) * 4); i; --i) {
|
||||
dst -= 6;
|
||||
src -= 4;
|
||||
lf = src[0];
|
||||
rf = src[1];
|
||||
lb = src[2];
|
||||
rb = src[3];
|
||||
ce = (lf + rf) * 0.5f;
|
||||
/* !!! FIXME: FL and FR may clip */
|
||||
dst[0] = lf + (lf - ce); /* FL */
|
||||
dst[1] = rf + (rf - ce); /* FR */
|
||||
dst[2] = ce; /* FC */
|
||||
dst[3] = 0; /* LFE (only meant for special LFE effects) */
|
||||
dst[4] = lb; /* BL */
|
||||
dst[5] = rb; /* BR */
|
||||
}
|
||||
|
||||
cvt->len_cvt = cvt->len_cvt * 3 / 2;
|
||||
if (cvt->filters[++cvt->filter_index]) {
|
||||
cvt->filters[cvt->filter_index] (cvt, format);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Upmix stereo to a pseudo-4.0 stream (by duplication) */
|
||||
static void SDLCALL
|
||||
SDL_ConvertStereoToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
|
@ -216,7 +307,7 @@ SDL_ConvertStereoToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
LOG_DEBUG_CONVERT("stereo", "quad");
|
||||
SDL_assert(format == AUDIO_F32SYS);
|
||||
|
||||
for (i = cvt->len_cvt / 8; i; --i) {
|
||||
for (i = cvt->len_cvt / (sizeof(float) * 2); i; --i) {
|
||||
dst -= 4;
|
||||
src -= 2;
|
||||
lf = src[0];
|
||||
|
@ -233,6 +324,52 @@ SDL_ConvertStereoToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* Upmix 5.1 to 7.1 */
|
||||
static void SDLCALL
|
||||
SDL_Convert51To71(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
||||
{
|
||||
float lf, rf, lb, rb, ls, rs;
|
||||
int i;
|
||||
const float *src = (const float *) (cvt->buf + cvt->len_cvt);
|
||||
float *dst = (float *) (cvt->buf + cvt->len_cvt * 4 / 3);
|
||||
|
||||
LOG_DEBUG_CONVERT("5.1", "7.1");
|
||||
SDL_assert(format == AUDIO_F32SYS);
|
||||
SDL_assert(cvt->len_cvt % (sizeof(float) * 6) == 0);
|
||||
|
||||
for (i = cvt->len_cvt / (sizeof(float) * 6); i; --i) {
|
||||
dst -= 8;
|
||||
src -= 6;
|
||||
lf = src[0];
|
||||
rf = src[1];
|
||||
lb = src[4];
|
||||
rb = src[5];
|
||||
ls = (lf + lb) * 0.5f;
|
||||
rs = (rf + rb) * 0.5f;
|
||||
/* !!! FIXME: these four may clip */
|
||||
lf += lf - ls;
|
||||
rf += rf - ls;
|
||||
lb += lb - ls;
|
||||
rb += rb - ls;
|
||||
dst[3] = src[3]; /* LFE */
|
||||
dst[2] = src[2]; /* FC */
|
||||
dst[7] = rs; /* SR */
|
||||
dst[6] = ls; /* SL */
|
||||
dst[5] = rb; /* BR */
|
||||
dst[4] = lb; /* BL */
|
||||
dst[1] = rf; /* FR */
|
||||
dst[0] = lf; /* FL */
|
||||
}
|
||||
|
||||
cvt->len_cvt = cvt->len_cvt * 4 / 3;
|
||||
|
||||
if (cvt->filters[++cvt->filter_index]) {
|
||||
cvt->filters[cvt->filter_index] (cvt, format);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
SDL_ResampleAudioSimple(const int chans, const double rate_incr,
|
||||
float *last_sample, const float *inbuf,
|
||||
|
@ -732,9 +869,9 @@ SDL_SupportedChannelCount(const int channels)
|
|||
case 2: /* stereo */
|
||||
case 4: /* quad */
|
||||
case 6: /* 5.1 */
|
||||
return SDL_TRUE; /* supported. */
|
||||
case 8: /* 7.1 */
|
||||
return SDL_TRUE; /* supported. */
|
||||
|
||||
case 8: /* !!! FIXME: 7.1 */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -857,7 +994,9 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
|
|||
}
|
||||
|
||||
/* Channel conversion */
|
||||
if (src_channels != dst_channels) {
|
||||
if (src_channels < dst_channels) {
|
||||
/* Upmixing */
|
||||
/* Mono -> Stereo [-> ...] */
|
||||
if ((src_channels == 1) && (dst_channels > 1)) {
|
||||
if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertMonoToStereo) < 0) {
|
||||
return -1;
|
||||
|
@ -866,7 +1005,8 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
|
|||
src_channels = 2;
|
||||
cvt->len_ratio *= 2;
|
||||
}
|
||||
if ((src_channels == 2) && (dst_channels == 6)) {
|
||||
/* [Mono ->] Stereo -> 5.1 [-> 7.1] */
|
||||
if ((src_channels == 2) && (dst_channels >= 6)) {
|
||||
if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertStereoTo51) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -874,6 +1014,27 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
|
|||
cvt->len_mult *= 3;
|
||||
cvt->len_ratio *= 3;
|
||||
}
|
||||
/* Quad -> 5.1 [-> 7.1] */
|
||||
if ((src_channels == 4) && (dst_channels >= 6)) {
|
||||
if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertQuadTo51) < 0) {
|
||||
return -1;
|
||||
}
|
||||
src_channels = 6;
|
||||
cvt->len_mult = (cvt->len_mult * 3 + 1) / 2;
|
||||
cvt->len_ratio *= 1.5;
|
||||
}
|
||||
/* [[Mono ->] Stereo ->] 5.1 -> 7.1 */
|
||||
if ((src_channels == 6) && (dst_channels == 8)) {
|
||||
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert51To71) < 0) {
|
||||
return -1;
|
||||
}
|
||||
src_channels = 8;
|
||||
cvt->len_mult = (cvt->len_mult * 4 + 2) / 3;
|
||||
/* Should be numerically exact with every valid input to this
|
||||
function */
|
||||
cvt->len_ratio = cvt->len_ratio * 4 / 3;
|
||||
}
|
||||
/* [Mono ->] Stereo -> Quad */
|
||||
if ((src_channels == 2) && (dst_channels == 4)) {
|
||||
if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertStereoToQuad) < 0) {
|
||||
return -1;
|
||||
|
@ -882,14 +1043,18 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
|
|||
cvt->len_mult *= 2;
|
||||
cvt->len_ratio *= 2;
|
||||
}
|
||||
while ((src_channels * 2) <= dst_channels) {
|
||||
if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertMonoToStereo) < 0) {
|
||||
} else if (src_channels > dst_channels) {
|
||||
/* Downmixing */
|
||||
/* 7.1 -> 5.1 [-> Stereo [-> Mono]] */
|
||||
/* 7.1 -> 5.1 [-> Quad] */
|
||||
if ((src_channels == 8) && (dst_channels <= 6)) {
|
||||
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert71To51) < 0) {
|
||||
return -1;
|
||||
}
|
||||
cvt->len_mult *= 2;
|
||||
src_channels *= 2;
|
||||
cvt->len_ratio *= 2;
|
||||
src_channels = 6;
|
||||
cvt->len_ratio *= 0.75;
|
||||
}
|
||||
/* [7.1 ->] 5.1 -> Stereo [-> Mono] */
|
||||
if ((src_channels == 6) && (dst_channels <= 2)) {
|
||||
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert51ToStereo) < 0) {
|
||||
return -1;
|
||||
|
@ -897,19 +1062,24 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
|
|||
src_channels = 2;
|
||||
cvt->len_ratio /= 3;
|
||||
}
|
||||
/* 5.1 -> Quad */
|
||||
if ((src_channels == 6) && (dst_channels == 4)) {
|
||||
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert51ToQuad) < 0) {
|
||||
return -1;
|
||||
}
|
||||
src_channels = 4;
|
||||
cvt->len_ratio = cvt->len_ratio * 2 / 3;
|
||||
}
|
||||
/* Quad -> Stereo [-> Mono] */
|
||||
if ((src_channels == 4) && (dst_channels <= 2)) {
|
||||
if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertQuadToStereo) < 0) {
|
||||
return -1;
|
||||
}
|
||||
src_channels = 2;
|
||||
cvt->len_ratio /= 2;
|
||||
}
|
||||
/* This assumes that 4 channel audio is in the format:
|
||||
Left {front/back} + Right {front/back}
|
||||
so converting to L/R stereo works properly.
|
||||
*/
|
||||
while (((src_channels % 2) == 0) &&
|
||||
((src_channels / 2) >= dst_channels)) {
|
||||
/* [... ->] Stereo -> Mono */
|
||||
if ((src_channels == 2) && (dst_channels == 1)) {
|
||||
SDL_AudioFilter filter = NULL;
|
||||
|
||||
#if HAVE_SSE3_INTRINSICS
|
||||
|
@ -926,14 +1096,17 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
|
|||
return -1;
|
||||
}
|
||||
|
||||
src_channels /= 2;
|
||||
src_channels = 1;
|
||||
cvt->len_ratio /= 2;
|
||||
}
|
||||
if (src_channels != dst_channels) {
|
||||
/* Uh oh.. */ ;
|
||||
}
|
||||
}
|
||||
|
||||
if (src_channels != dst_channels) {
|
||||
/* All combinations of supported channel counts should have been
|
||||
handled by now, but let's be defensive */
|
||||
return SDL_SetError("Invalid channel combination");
|
||||
}
|
||||
|
||||
/* Do rate conversion, if necessary. Updates (cvt). */
|
||||
if (SDL_BuildAudioResampleCVT(cvt, dst_channels, src_rate, dst_rate) < 0) {
|
||||
return -1; /* shouldn't happen, but just in case... */
|
||||
|
|
Loading…
Reference in New Issue