diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index 0a999730c..b59a1e12b 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -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... */