mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
WASAPI: Fix channel mapping for 5.1 audi
CRITICAL BUG FIX: The previous code assumed incorrect channel ordering for multichannel audio, breaking 5.1 (6-channel) configurations. Problem: - Code assumed channel order: FL, FR, RL, RR, C, LFE o systems- Actual 5.1 standard order: FL, FR, C, LFE, RL, RR - This caused center/LFE to receive rear audio and vice versa - Users had to force stereo mode to get working audio Solution: - Parse dwChannelMask from WAVEFORMATEXTENSIBLE to determine actual channel positions - Map stereo input to correct output channels based on the mask - Properly handles 5.1, 7.1, and other multichannel configurations - Safely handles non-standard channel layouts The channel mapping now works correctly for all standard speaker configurations by querying the device's actual channel layout instead of assuming a fixed order.
This commit is contained in:
1 parent
11d4107230
commit
32f3fbd65a
1 file changed
+118
-62
+118
-62
@@ -23,6 +23,56 @@ static constexpr float SURROUND_ATTENUATION = 0.7f; // For rear/side channels
|
||||
static constexpr float CENTER_MIX_ATTENUATION = 0.7f; // For center channel mix
|
||||
static constexpr float LFE_MIX_ATTENUATION = 0.5f; // For LFE channel mix
|
||||
|
||||
// Helper to determine channel positions from WAVEFORMATEXTENSIBLE
|
||||
struct ChannelMapping {
|
||||
int frontLeft = -1;
|
||||
int frontRight = -1;
|
||||
int center = -1;
|
||||
int lfe = -1;
|
||||
int rearLeft = -1;
|
||||
int rearRight = -1;
|
||||
};
|
||||
|
||||
static ChannelMapping GetChannelMapping(const WAVEFORMATEX *format) {
|
||||
ChannelMapping mapping;
|
||||
|
||||
if (!format) {
|
||||
return mapping;
|
||||
}
|
||||
|
||||
// For non-extensible formats, assume standard stereo/mono
|
||||
if (format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
|
||||
if (format->nChannels >= 1) mapping.frontLeft = 0;
|
||||
if (format->nChannels >= 2) mapping.frontRight = 1;
|
||||
return mapping;
|
||||
}
|
||||
|
||||
const WAVEFORMATEXTENSIBLE *formatEx = reinterpret_cast<const WAVEFORMATEXTENSIBLE*>(format);
|
||||
const DWORD mask = formatEx->dwChannelMask;
|
||||
|
||||
// Build mapping based on channel mask
|
||||
int channelIndex = 0;
|
||||
|
||||
// Front speakers
|
||||
if (mask & SPEAKER_FRONT_LEFT) mapping.frontLeft = channelIndex++;
|
||||
if (mask & SPEAKER_FRONT_RIGHT) mapping.frontRight = channelIndex++;
|
||||
if (mask & SPEAKER_FRONT_CENTER) mapping.center = channelIndex++;
|
||||
if (mask & SPEAKER_LOW_FREQUENCY) mapping.lfe = channelIndex++;
|
||||
|
||||
// Rear/back speakers
|
||||
if (mask & SPEAKER_BACK_LEFT) mapping.rearLeft = channelIndex++;
|
||||
if (mask & SPEAKER_BACK_RIGHT) mapping.rearRight = channelIndex++;
|
||||
|
||||
// Side speakers (if no back, use these as rear)
|
||||
if (mapping.rearLeft == -1 && (mask & SPEAKER_SIDE_LEFT)) mapping.rearLeft = channelIndex++;
|
||||
if (mapping.rearRight == -1 && (mask & SPEAKER_SIDE_RIGHT)) mapping.rearRight = channelIndex++;
|
||||
|
||||
// Note: We skip other speaker positions (top, front center of height, etc.)
|
||||
// as they're rarely used and we're just doing stereo upmix anyway
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
// We must have one of these already...
|
||||
static inline s16 ClampFloatToS16(float f) {
|
||||
f *= 32768.0f;
|
||||
@@ -635,6 +685,7 @@ void WASAPIContext::AudioLoop() {
|
||||
|
||||
const AudioFormat format = Classify(format_);
|
||||
const int nChannels = curChannels_.load();
|
||||
const ChannelMapping channelMap = GetChannelMapping(format_);
|
||||
|
||||
ClearErrorString();
|
||||
|
||||
@@ -692,44 +743,46 @@ void WASAPIContext::AudioLoop() {
|
||||
// We decided previously that we need conversion, so mix to our temp buffer...
|
||||
callback_(tempBuf_.get(), framesToWrite, format_->nSamplesPerSec, userdata_);
|
||||
// .. and convert according to format (we support multi-channel float and s16)
|
||||
// NOTE: Channel mapping assumes standard order (FL, FR, RL, RR, C, LFE).
|
||||
// Non-standard channel masks from WAVEFORMATEXTENSIBLE are not currently handled.
|
||||
// Use the channel mapping to place audio in the correct channels
|
||||
if (format == AudioFormat::PCM16 && buffer) {
|
||||
// Need to convert.
|
||||
s16 *dest = reinterpret_cast<s16 *>(buffer);
|
||||
for (UINT32 i = 0; i < framesToWrite; i++) {
|
||||
if (nChannels == 1) {
|
||||
// Maybe some bluetooth speakers? Mixdown.
|
||||
float sum = 0.5f * (tempBuf_[i * 2] + tempBuf_[i * 2 + 1]);
|
||||
dest[i] = ClampFloatToS16(sum);
|
||||
} else if (nChannels == 2) {
|
||||
// Stereo output
|
||||
dest[i * 2] = ClampFloatToS16(tempBuf_[i * 2]);
|
||||
dest[i * 2 + 1] = ClampFloatToS16(tempBuf_[i * 2 + 1]);
|
||||
} else {
|
||||
// Multi-channel output (e.g., 5.1, 7.1)
|
||||
// Copy stereo to front L/R channels
|
||||
dest[i * nChannels] = ClampFloatToS16(tempBuf_[i * 2]); // Front Left
|
||||
dest[i * nChannels + 1] = ClampFloatToS16(tempBuf_[i * 2 + 1]); // Front Right
|
||||
// Zero the entire frame first
|
||||
for (int j = 0; j < nChannels; j++) {
|
||||
dest[i * nChannels + j] = 0;
|
||||
}
|
||||
|
||||
// For 5.1/7.1 systems, also send audio to rear channels
|
||||
if (nChannels >= 4) {
|
||||
// Rear/Surround Left and Right at reduced volume
|
||||
dest[i * nChannels + 2] = ClampFloatToS16(tempBuf_[i * 2] * SURROUND_ATTENUATION);
|
||||
dest[i * nChannels + 3] = ClampFloatToS16(tempBuf_[i * 2 + 1] * SURROUND_ATTENUATION);
|
||||
// Map stereo input to appropriate output channels
|
||||
const float left = tempBuf_[i * 2];
|
||||
const float right = tempBuf_[i * 2 + 1];
|
||||
|
||||
if (nChannels == 1) {
|
||||
// Mono: mixdown stereo to mono
|
||||
dest[i] = ClampFloatToS16((left + right) * 0.5f);
|
||||
} else if (nChannels == 2) {
|
||||
// Stereo: direct copy
|
||||
dest[i * 2] = ClampFloatToS16(left);
|
||||
dest[i * 2 + 1] = ClampFloatToS16(right);
|
||||
} else {
|
||||
// Multi-channel: use channel mapping
|
||||
if (channelMap.frontLeft >= 0) {
|
||||
dest[i * nChannels + channelMap.frontLeft] = ClampFloatToS16(left);
|
||||
}
|
||||
// Center and LFE (if present)
|
||||
for (int j = 4; j < nChannels; j++) {
|
||||
if (j == 4 && nChannels >= 6) {
|
||||
// Center channel - mix of L+R at reduced volume
|
||||
dest[i * nChannels + j] = ClampFloatToS16((tempBuf_[i * 2] + tempBuf_[i * 2 + 1]) * 0.5f * CENTER_MIX_ATTENUATION);
|
||||
} else if (j == 5 && nChannels >= 6) {
|
||||
// LFE channel - bass from L+R at reduced volume
|
||||
dest[i * nChannels + j] = ClampFloatToS16((tempBuf_[i * 2] + tempBuf_[i * 2 + 1]) * 0.5f * LFE_MIX_ATTENUATION);
|
||||
} else {
|
||||
// Any extra channels get zeroed
|
||||
dest[i * nChannels + j] = 0;
|
||||
}
|
||||
if (channelMap.frontRight >= 0) {
|
||||
dest[i * nChannels + channelMap.frontRight] = ClampFloatToS16(right);
|
||||
}
|
||||
if (channelMap.center >= 0) {
|
||||
dest[i * nChannels + channelMap.center] = ClampFloatToS16((left + right) * 0.5f * CENTER_MIX_ATTENUATION);
|
||||
}
|
||||
if (channelMap.lfe >= 0) {
|
||||
dest[i * nChannels + channelMap.lfe] = ClampFloatToS16((left + right) * 0.5f * LFE_MIX_ATTENUATION);
|
||||
}
|
||||
if (channelMap.rearLeft >= 0) {
|
||||
dest[i * nChannels + channelMap.rearLeft] = ClampFloatToS16(left * SURROUND_ATTENUATION);
|
||||
}
|
||||
if (channelMap.rearRight >= 0) {
|
||||
dest[i * nChannels + channelMap.rearRight] = ClampFloatToS16(right * SURROUND_ATTENUATION);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -737,38 +790,41 @@ void WASAPIContext::AudioLoop() {
|
||||
// We have a non-2 number of channels (since we're in the tempBuf_ 'if'), so we contract/expand.
|
||||
float *dest = reinterpret_cast<float *>(buffer);
|
||||
for (UINT32 i = 0; i < framesToWrite; i++) {
|
||||
if (nChannels == 1) {
|
||||
// Maybe some bluetooth speakers? Mixdown.
|
||||
dest[i] = 0.5f * (tempBuf_[i * 2] + tempBuf_[i * 2 + 1]);
|
||||
} else if (nChannels == 2) {
|
||||
// Stereo output
|
||||
dest[i * 2] = tempBuf_[i * 2];
|
||||
dest[i * 2 + 1] = tempBuf_[i * 2 + 1];
|
||||
} else {
|
||||
// Multi-channel output (e.g., 5.1, 7.1)
|
||||
// Copy stereo to front L/R channels
|
||||
dest[i * nChannels] = tempBuf_[i * 2]; // Front Left
|
||||
dest[i * nChannels + 1] = tempBuf_[i * 2 + 1]; // Front Right
|
||||
// Zero the entire frame first
|
||||
for (int j = 0; j < nChannels; j++) {
|
||||
dest[i * nChannels + j] = 0.0f;
|
||||
}
|
||||
|
||||
// For 5.1/7.1 systems, also send audio to rear channels
|
||||
// This prevents the "half silent" buffer issue that can cause crackling
|
||||
if (nChannels >= 4) {
|
||||
// Rear/Surround Left and Right at reduced volume
|
||||
dest[i * nChannels + 2] = tempBuf_[i * 2] * SURROUND_ATTENUATION; // Rear/Side Left
|
||||
dest[i * nChannels + 3] = tempBuf_[i * 2 + 1] * SURROUND_ATTENUATION; // Rear/Side Right
|
||||
// Map stereo input to appropriate output channels
|
||||
const float left = tempBuf_[i * 2];
|
||||
const float right = tempBuf_[i * 2 + 1];
|
||||
|
||||
if (nChannels == 1) {
|
||||
// Mono: mixdown stereo to mono
|
||||
dest[i] = (left + right) * 0.5f;
|
||||
} else if (nChannels == 2) {
|
||||
// Stereo: direct copy
|
||||
dest[i * 2] = left;
|
||||
dest[i * 2 + 1] = right;
|
||||
} else {
|
||||
// Multi-channel: use channel mapping
|
||||
if (channelMap.frontLeft >= 0) {
|
||||
dest[i * nChannels + channelMap.frontLeft] = left;
|
||||
}
|
||||
// Center and LFE (if present)
|
||||
for (int j = 4; j < nChannels; j++) {
|
||||
if (j == 4 && nChannels >= 6) {
|
||||
// Center channel - mix of L+R at reduced volume
|
||||
dest[i * nChannels + j] = (tempBuf_[i * 2] + tempBuf_[i * 2 + 1]) * 0.5f * CENTER_MIX_ATTENUATION;
|
||||
} else if (j == 5 && nChannels >= 6) {
|
||||
// LFE channel - bass from L+R at reduced volume
|
||||
dest[i * nChannels + j] = (tempBuf_[i * 2] + tempBuf_[i * 2 + 1]) * 0.5f * LFE_MIX_ATTENUATION;
|
||||
} else {
|
||||
// Any extra channels get zeroed
|
||||
dest[i * nChannels + j] = 0;
|
||||
}
|
||||
if (channelMap.frontRight >= 0) {
|
||||
dest[i * nChannels + channelMap.frontRight] = right;
|
||||
}
|
||||
if (channelMap.center >= 0) {
|
||||
dest[i * nChannels + channelMap.center] = (left + right) * 0.5f * CENTER_MIX_ATTENUATION;
|
||||
}
|
||||
if (channelMap.lfe >= 0) {
|
||||
dest[i * nChannels + channelMap.lfe] = (left + right) * 0.5f * LFE_MIX_ATTENUATION;
|
||||
}
|
||||
if (channelMap.rearLeft >= 0) {
|
||||
dest[i * nChannels + channelMap.rearLeft] = left * SURROUND_ATTENUATION;
|
||||
}
|
||||
if (channelMap.rearRight >= 0) {
|
||||
dest[i * nChannels + channelMap.rearRight] = right * SURROUND_ATTENUATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user