Fixes sound in Touhou Luna Nights with some surround sound configurations.
Signed-off-by: Andrew Eikum aeikum@codeweavers.com ---
v2: Check max channels on OSS, too.
dlls/winealsa.drv/mmdevdrv.c | 16 +++ dlls/winecoreaudio.drv/mmdevdrv.c | 197 ++++++++++++++++++++++++++---- dlls/wineoss.drv/mmdevdrv.c | 16 +++ dlls/winepulse.drv/mmdevdrv.c | 97 ++++++++++++++- 4 files changed, 296 insertions(+), 30 deletions(-)
diff --git a/dlls/winealsa.drv/mmdevdrv.c b/dlls/winealsa.drv/mmdevdrv.c index 91aef4788d8..4f1270b33c8 100644 --- a/dlls/winealsa.drv/mmdevdrv.c +++ b/dlls/winealsa.drv/mmdevdrv.c @@ -1831,6 +1831,22 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient *iface, else fmt->Format.nChannels = max_channels;
+ if(fmt->Format.nChannels > 1 && (fmt->Format.nChannels & 0x1)){ + /* For most hardware on Windows, users must choose a configuration with an even + * number of channels (stereo, quad, 5.1, 7.1). Users can then disable + * channels, but those channels are still reported to applications from + * GetMixFormat! Some applications behave badly if given an odd number of + * channels (e.g. 2.1). */ + + if(fmt->Format.nChannels < max_channels) + fmt->Format.nChannels += 1; + else + /* We could "fake" more channels and downmix the emulated channels, + * but at that point you really ought to tweak your ALSA setup or + * just use PulseAudio. */ + WARN("Some Windows applications behave badly with an odd number of channels (%u)!\n", fmt->Format.nChannels); + } + fmt->dwChannelMask = get_channel_mask(fmt->Format.nChannels);
if((err = snd_pcm_hw_params_get_rate_max(This->hw_params, &max_rate, diff --git a/dlls/winecoreaudio.drv/mmdevdrv.c b/dlls/winecoreaudio.drv/mmdevdrv.c index 2f4683af976..be2b5e7e1e9 100644 --- a/dlls/winecoreaudio.drv/mmdevdrv.c +++ b/dlls/winecoreaudio.drv/mmdevdrv.c @@ -1736,6 +1736,122 @@ unsupported: return AUDCLNT_E_UNSUPPORTED_FORMAT; }
+static DWORD ca_channel_layout_to_channel_mask(const AudioChannelLayout *layout) +{ + int i; + DWORD mask = 0; + + for (i = 0; i < layout->mNumberChannelDescriptions; ++i) { + switch (layout->mChannelDescriptions[i].mChannelLabel) { + default: FIXME("Unhandled channel 0x%x\n", layout->mChannelDescriptions[i].mChannelLabel); break; + case kAudioChannelLabel_Left: mask |= SPEAKER_FRONT_LEFT; break; + case kAudioChannelLabel_Mono: + case kAudioChannelLabel_Center: mask |= SPEAKER_FRONT_CENTER; break; + case kAudioChannelLabel_Right: mask |= SPEAKER_FRONT_RIGHT; break; + case kAudioChannelLabel_LeftSurround: mask |= SPEAKER_BACK_LEFT; break; + case kAudioChannelLabel_CenterSurround: mask |= SPEAKER_BACK_CENTER; break; + case kAudioChannelLabel_RightSurround: mask |= SPEAKER_BACK_RIGHT; break; + case kAudioChannelLabel_LFEScreen: mask |= SPEAKER_LOW_FREQUENCY; break; + case kAudioChannelLabel_LeftSurroundDirect: mask |= SPEAKER_SIDE_LEFT; break; + case kAudioChannelLabel_RightSurroundDirect: mask |= SPEAKER_SIDE_RIGHT; break; + case kAudioChannelLabel_TopCenterSurround: mask |= SPEAKER_TOP_CENTER; break; + case kAudioChannelLabel_VerticalHeightLeft: mask |= SPEAKER_TOP_FRONT_LEFT; break; + case kAudioChannelLabel_VerticalHeightCenter: mask |= SPEAKER_TOP_FRONT_CENTER; break; + case kAudioChannelLabel_VerticalHeightRight: mask |= SPEAKER_TOP_FRONT_RIGHT; break; + case kAudioChannelLabel_TopBackLeft: mask |= SPEAKER_TOP_BACK_LEFT; break; + case kAudioChannelLabel_TopBackCenter: mask |= SPEAKER_TOP_BACK_CENTER; break; + case kAudioChannelLabel_TopBackRight: mask |= SPEAKER_TOP_BACK_RIGHT; break; + case kAudioChannelLabel_LeftCenter: mask |= SPEAKER_FRONT_LEFT_OF_CENTER; break; + case kAudioChannelLabel_RightCenter: mask |= SPEAKER_FRONT_RIGHT_OF_CENTER; break; + } + } + + return mask; +} + +/* For most hardware on Windows, users must choose a configuration with an even + * number of channels (stereo, quad, 5.1, 7.1). Users can then disable + * channels, but those channels are still reported to applications from + * GetMixFormat! Some applications behave badly if given an odd number of + * channels (e.g. 2.1). Here, we find the nearest configuration that Windows + * would report for a given channel layout. */ +static void convert_channel_layout(const AudioChannelLayout *ca_layout, WAVEFORMATEXTENSIBLE *fmt) +{ + DWORD ca_mask = ca_channel_layout_to_channel_mask(ca_layout); + + TRACE("Got channel mask for CA: 0x%x\n", ca_mask); + + if (ca_layout->mNumberChannelDescriptions == 1) + { + fmt->Format.nChannels = 1; + fmt->dwChannelMask = ca_mask; + return; + } + + /* compare against known configurations and find smallest configuration + * which is a superset of the given speakers */ + + if (ca_layout->mNumberChannelDescriptions <= 2 && + (ca_mask & ~KSAUDIO_SPEAKER_STEREO) == 0) + { + fmt->Format.nChannels = 2; + fmt->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 4 && + (ca_mask & ~KSAUDIO_SPEAKER_QUAD) == 0) + { + fmt->Format.nChannels = 4; + fmt->dwChannelMask = KSAUDIO_SPEAKER_QUAD; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 4 && + (ca_mask & ~KSAUDIO_SPEAKER_SURROUND) == 0) + { + fmt->Format.nChannels = 4; + fmt->dwChannelMask = KSAUDIO_SPEAKER_SURROUND; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 6 && + (ca_mask & ~KSAUDIO_SPEAKER_5POINT1) == 0) + { + fmt->Format.nChannels = 6; + fmt->dwChannelMask = KSAUDIO_SPEAKER_5POINT1; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 6 && + (ca_mask & ~KSAUDIO_SPEAKER_5POINT1_SURROUND) == 0) + { + fmt->Format.nChannels = 6; + fmt->dwChannelMask = KSAUDIO_SPEAKER_5POINT1_SURROUND; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 8 && + (ca_mask & ~KSAUDIO_SPEAKER_7POINT1) == 0) + { + fmt->Format.nChannels = 8; + fmt->dwChannelMask = KSAUDIO_SPEAKER_7POINT1; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 8 && + (ca_mask & ~KSAUDIO_SPEAKER_7POINT1_SURROUND) == 0) + { + fmt->Format.nChannels = 8; + fmt->dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND; + return; + } + + /* oddball format, report truthfully */ + fmt->Format.nChannels = ca_layout->mNumberChannelDescriptions; + fmt->dwChannelMask = ca_mask; +} + static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient *iface, WAVEFORMATEX **pwfx) { @@ -1745,6 +1861,7 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient *iface, UInt32 size; Float64 rate; AudioBufferList *buffers; + AudioChannelLayout *layout; AudioObjectPropertyAddress addr; int i;
@@ -1762,38 +1879,70 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient *iface,
addr.mScope = This->scope; addr.mElement = 0; - addr.mSelector = kAudioDevicePropertyStreamConfiguration; + addr.mSelector = kAudioDevicePropertyPreferredChannelLayout;
sc = AudioObjectGetPropertyDataSize(This->adevid, &addr, 0, NULL, &size); - if(sc != noErr){ - CoTaskMemFree(fmt); - WARN("Unable to get size for _StreamConfiguration property: %x\n", (int)sc); - return osstatus_to_hresult(sc); - } + if(sc == noErr){ + layout = HeapAlloc(GetProcessHeap(), 0, size); + + sc = AudioObjectGetPropertyData(This->adevid, &addr, 0, NULL, &size, layout); + if(sc == noErr){ + TRACE("Got channel layout: {tag: 0x%x, bitmap: 0x%x, num_descs: %u}\n", + layout->mChannelLayoutTag, layout->mChannelBitmap, layout->mNumberChannelDescriptions);
- buffers = HeapAlloc(GetProcessHeap(), 0, size); - if(!buffers){ - CoTaskMemFree(fmt); - return E_OUTOFMEMORY; + if(layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions){ + convert_channel_layout(layout, fmt); + }else{ + WARN("Haven't implemented support for this layout tag: 0x%x, guessing at layout\n", layout->mChannelLayoutTag); + fmt->Format.nChannels = 0; + } + }else{ + TRACE("Unable to get _PreferredChannelLayout property: %x, guessing at layout\n", (int)sc); + fmt->Format.nChannels = 0; + } + + HeapFree(GetProcessHeap(), 0, layout); + }else{ + TRACE("Unable to get size for _PreferredChannelLayout property: %x, guessing at layout\n", (int)sc); + fmt->Format.nChannels = 0; }
- sc = AudioObjectGetPropertyData(This->adevid, &addr, 0, NULL, - &size, buffers); - if(sc != noErr){ - CoTaskMemFree(fmt); + if(fmt->Format.nChannels == 0){ + addr.mScope = This->scope; + addr.mElement = 0; + addr.mSelector = kAudioDevicePropertyStreamConfiguration; + + sc = AudioObjectGetPropertyDataSize(This->adevid, &addr, 0, NULL, &size); + if(sc != noErr){ + CoTaskMemFree(fmt); + WARN("Unable to get size for _StreamConfiguration property: %x\n", (int)sc); + return osstatus_to_hresult(sc); + } + + buffers = HeapAlloc(GetProcessHeap(), 0, size); + if(!buffers){ + CoTaskMemFree(fmt); + return E_OUTOFMEMORY; + } + + sc = AudioObjectGetPropertyData(This->adevid, &addr, 0, NULL, + &size, buffers); + if(sc != noErr){ + CoTaskMemFree(fmt); + HeapFree(GetProcessHeap(), 0, buffers); + WARN("Unable to get _StreamConfiguration property: %x\n", (int)sc); + return osstatus_to_hresult(sc); + } + + fmt->Format.nChannels = 0; + for(i = 0; i < buffers->mNumberBuffers; ++i) + fmt->Format.nChannels += buffers->mBuffers[i].mNumberChannels; + HeapFree(GetProcessHeap(), 0, buffers); - WARN("Unable to get _StreamConfiguration property: %x\n", (int)sc); - return osstatus_to_hresult(sc); + + fmt->dwChannelMask = get_channel_mask(fmt->Format.nChannels); }
- fmt->Format.nChannels = 0; - for(i = 0; i < buffers->mNumberBuffers; ++i) - fmt->Format.nChannels += buffers->mBuffers[i].mNumberChannels; - - HeapFree(GetProcessHeap(), 0, buffers); - - fmt->dwChannelMask = get_channel_mask(fmt->Format.nChannels); - addr.mSelector = kAudioDevicePropertyNominalSampleRate; size = sizeof(Float64); sc = AudioObjectGetPropertyData(This->adevid, &addr, 0, NULL, &size, &rate); diff --git a/dlls/wineoss.drv/mmdevdrv.c b/dlls/wineoss.drv/mmdevdrv.c index afa018ab0f9..dca40ecd771 100644 --- a/dlls/wineoss.drv/mmdevdrv.c +++ b/dlls/wineoss.drv/mmdevdrv.c @@ -1334,6 +1334,22 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient *iface, if(fmt->Format.nChannels == 0 || fmt->Format.nChannels > 8) fmt->Format.nChannels = 2;
+ /* For most hardware on Windows, users must choose a configuration with an even + * number of channels (stereo, quad, 5.1, 7.1). Users can then disable + * channels, but those channels are still reported to applications from + * GetMixFormat! Some applications behave badly if given an odd number of + * channels (e.g. 2.1). */ + if(fmt->Format.nChannels > 1 && (fmt->Format.nChannels & 0x1)) + { + if(fmt->Format.nChannels < This->ai.max_channels) + fmt->Format.nChannels += 1; + else + /* We could "fake" more channels and downmix the emulated channels, + * but at that point you really ought to tweak your OSS setup or + * just use PulseAudio. */ + WARN("Some Windows applications behave badly with an odd number of channels (%u)!\n", fmt->Format.nChannels); + } + if(This->ai.max_rate == 0) fmt->Format.nSamplesPerSec = 44100; else diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 01f0d1b64c2..1c647c73d62 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -341,11 +341,12 @@ static const enum pa_channel_position pulse_pos_from_wfx[] = { PA_CHANNEL_POSITION_TOP_REAR_RIGHT };
-static DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) { +static DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) +{ int i; DWORD mask = 0;
- for (i = 0; i < map->channels; ++i) + for (i = 0; i < map->channels; ++i) { switch (map->map[i]) { default: FIXME("Unhandled channel %s\n", pa_channel_position_to_string(map->map[i])); break; case PA_CHANNEL_POSITION_FRONT_LEFT: mask |= SPEAKER_FRONT_LEFT; break; @@ -367,11 +368,95 @@ static DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) { case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: mask |= SPEAKER_TOP_BACK_RIGHT; break; case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: mask |= SPEAKER_FRONT_LEFT_OF_CENTER; break; case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: mask |= SPEAKER_FRONT_RIGHT_OF_CENTER; break; + } }
return mask; }
+/* For most hardware on Windows, users must choose a configuration with an even + * number of channels (stereo, quad, 5.1, 7.1). Users can then disable + * channels, but those channels are still reported to applications from + * GetMixFormat! Some applications behave badly if given an odd number of + * channels (e.g. 2.1). Here, we find the nearest configuration that Windows + * would report for a given channel layout. */ +static void convert_channel_map(const pa_channel_map *pa_map, WAVEFORMATEXTENSIBLE *fmt) +{ + DWORD pa_mask = pulse_channel_map_to_channel_mask(pa_map); + + TRACE("got mask for PA: 0x%x\n", pa_mask); + + if (pa_map->channels == 1) + { + fmt->Format.nChannels = 1; + fmt->dwChannelMask = pa_mask; + return; + } + + /* compare against known configurations and find smallest configuration + * which is a superset of the given speakers */ + + if (pa_map->channels <= 2 && + (pa_mask & ~KSAUDIO_SPEAKER_STEREO) == 0) + { + fmt->Format.nChannels = 2; + fmt->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + return; + } + + if (pa_map->channels <= 4 && + (pa_mask & ~KSAUDIO_SPEAKER_QUAD) == 0) + { + fmt->Format.nChannels = 4; + fmt->dwChannelMask = KSAUDIO_SPEAKER_QUAD; + return; + } + + if (pa_map->channels <= 4 && + (pa_mask & ~KSAUDIO_SPEAKER_SURROUND) == 0) + { + fmt->Format.nChannels = 4; + fmt->dwChannelMask = KSAUDIO_SPEAKER_SURROUND; + return; + } + + if (pa_map->channels <= 6 && + (pa_mask & ~KSAUDIO_SPEAKER_5POINT1) == 0) + { + fmt->Format.nChannels = 6; + fmt->dwChannelMask = KSAUDIO_SPEAKER_5POINT1; + return; + } + + if (pa_map->channels <= 6 && + (pa_mask & ~KSAUDIO_SPEAKER_5POINT1_SURROUND) == 0) + { + fmt->Format.nChannels = 6; + fmt->dwChannelMask = KSAUDIO_SPEAKER_5POINT1_SURROUND; + return; + } + + if (pa_map->channels <= 8 && + (pa_mask & ~KSAUDIO_SPEAKER_7POINT1) == 0) + { + fmt->Format.nChannels = 8; + fmt->dwChannelMask = KSAUDIO_SPEAKER_7POINT1; + return; + } + + if (pa_map->channels <= 8 && + (pa_mask & ~KSAUDIO_SPEAKER_7POINT1_SURROUND) == 0) + { + fmt->Format.nChannels = 8; + fmt->dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND; + return; + } + + /* oddball format, report truthfully */ + fmt->Format.nChannels = pa_map->channels; + fmt->dwChannelMask = pa_mask; +} + static void pulse_probe_settings(int render, WAVEFORMATEXTENSIBLE *fmt) { WAVEFORMATEX *wfx = &fmt->Format; pa_stream *stream; @@ -433,10 +518,12 @@ static void pulse_probe_settings(int render, WAVEFORMATEXTENSIBLE *fmt) {
wfx->wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfx->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - wfx->nChannels = ss.channels; + + convert_channel_map(&map, fmt); + wfx->wBitsPerSample = 8 * pa_sample_size_of_format(ss.format); wfx->nSamplesPerSec = ss.rate; - wfx->nBlockAlign = pa_frame_size(&ss); + wfx->nBlockAlign = wfx->nChannels * wfx->wBitsPerSample / 8; wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign; if (ss.format != PA_SAMPLE_S24_32LE) fmt->Samples.wValidBitsPerSample = wfx->wBitsPerSample; @@ -446,8 +533,6 @@ static void pulse_probe_settings(int render, WAVEFORMATEXTENSIBLE *fmt) { fmt->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; else fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - - fmt->dwChannelMask = pulse_channel_map_to_channel_mask(&map); }
static HRESULT pulse_connect(void)