[PATCH v2] mmdevapi: Convert channel layouts to Windows compatible layouts

Andrew Eikum aeikum at codeweavers.com
Tue Sep 10 11:12:25 CDT 2019


I'd prefer the subject from v1, "mmdevapi: Avoid reporting odd numbers
of channels". Sorry for the mess.

Andrew

On Tue, Sep 10, 2019 at 10:55:16AM -0500, Andrew Eikum wrote:
> Fixes sound in Touhou Luna Nights with some surround sound
> configurations.
> 
> Signed-off-by: Andrew Eikum <aeikum at 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)
> -- 
> 2.23.0
> 
> 



More information about the wine-devel mailing list