diff --git a/dlls/winealsa.drv/alsa.c b/dlls/winealsa.drv/alsa.c index 320d676effb..0680383f59b 100644 --- a/dlls/winealsa.drv/alsa.c +++ b/dlls/winealsa.drv/alsa.c @@ -453,6 +453,27 @@ static NTSTATUS get_endpoint_ids(void *args) return STATUS_SUCCESS; } +static WAVEFORMATEXTENSIBLE *clone_format(const WAVEFORMATEX *fmt) +{ + WAVEFORMATEXTENSIBLE *ret; + size_t size; + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + size = sizeof(WAVEFORMATEXTENSIBLE); + else + size = sizeof(WAVEFORMATEX); + + ret = malloc(size); + if(!ret) + return NULL; + + memcpy(ret, fmt, size); + + ret->Format.cbSize = size - sizeof(WAVEFORMATEX); + + return ret; +} + static HRESULT alsa_open_device(const char *alsa_name, EDataFlow flow, snd_pcm_t **pcm_handle, snd_pcm_hw_params_t **hw_params) { @@ -486,6 +507,78 @@ static HRESULT alsa_open_device(const char *alsa_name, EDataFlow flow, snd_pcm_t return S_OK; } +static snd_pcm_format_t alsa_format(const WAVEFORMATEX *fmt) +{ + snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN; + const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt; + + if(fmt->wFormatTag == WAVE_FORMAT_PCM || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))){ + if(fmt->wBitsPerSample == 8) + format = SND_PCM_FORMAT_U8; + else if(fmt->wBitsPerSample == 16) + format = SND_PCM_FORMAT_S16_LE; + else if(fmt->wBitsPerSample == 24) + format = SND_PCM_FORMAT_S24_3LE; + else if(fmt->wBitsPerSample == 32) + format = SND_PCM_FORMAT_S32_LE; + else + WARN("Unsupported bit depth: %u\n", fmt->wBitsPerSample); + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + fmt->wBitsPerSample != fmtex->Samples.wValidBitsPerSample){ + if(fmtex->Samples.wValidBitsPerSample == 20 && fmt->wBitsPerSample == 24) + format = SND_PCM_FORMAT_S20_3LE; + else + WARN("Unsupported ValidBits: %u\n", fmtex->Samples.wValidBitsPerSample); + } + }else if(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))){ + if(fmt->wBitsPerSample == 32) + format = SND_PCM_FORMAT_FLOAT_LE; + else if(fmt->wBitsPerSample == 64) + format = SND_PCM_FORMAT_FLOAT64_LE; + else + WARN("Unsupported float size: %u\n", fmt->wBitsPerSample); + }else + WARN("Unknown wave format: %04x\n", fmt->wFormatTag); + return format; +} + +static int alsa_channel_index(DWORD flag) +{ + switch(flag){ + case SPEAKER_FRONT_LEFT: + return 0; + case SPEAKER_FRONT_RIGHT: + return 1; + case SPEAKER_BACK_LEFT: + return 2; + case SPEAKER_BACK_RIGHT: + return 3; + case SPEAKER_FRONT_CENTER: + return 4; + case SPEAKER_LOW_FREQUENCY: + return 5; + case SPEAKER_SIDE_LEFT: + return 6; + case SPEAKER_SIDE_RIGHT: + return 7; + } + return -1; +} + +static BOOL need_remapping(const WAVEFORMATEX *fmt, int *map) +{ + unsigned int i; + for(i = 0; i < fmt->nChannels; ++i){ + if(map[i] != i) + return TRUE; + } + return FALSE; +} + static DWORD get_channel_mask(unsigned int channels) { switch(channels){ @@ -512,6 +605,203 @@ static DWORD get_channel_mask(unsigned int channels) return 0; } +static HRESULT map_channels(EDataFlow flow, const WAVEFORMATEX *fmt, int *alsa_channels, int *map) +{ + BOOL need_remap; + + if(flow != eCapture && (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE || fmt->nChannels > 2) ){ + WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt; + DWORD mask, flag = SPEAKER_FRONT_LEFT; + UINT i = 0; + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + fmtex->dwChannelMask != 0) + mask = fmtex->dwChannelMask; + else + mask = get_channel_mask(fmt->nChannels); + + *alsa_channels = 0; + + while(i < fmt->nChannels && !(flag & SPEAKER_RESERVED)){ + if(mask & flag){ + map[i] = alsa_channel_index(flag); + TRACE("Mapping mmdevapi channel %u (0x%x) to ALSA channel %d\n", + i, flag, map[i]); + if(map[i] >= *alsa_channels) + *alsa_channels = map[i] + 1; + ++i; + } + flag <<= 1; + } + + while(i < fmt->nChannels){ + map[i] = *alsa_channels; + TRACE("Mapping mmdevapi channel %u to ALSA channel %d\n", + i, map[i]); + ++*alsa_channels; + ++i; + } + + for(i = 0; i < fmt->nChannels; ++i){ + if(map[i] == -1){ + map[i] = *alsa_channels; + ++*alsa_channels; + TRACE("Remapping mmdevapi channel %u to ALSA channel %d\n", + i, map[i]); + } + } + + need_remap = need_remapping(fmt, map); + }else{ + *alsa_channels = fmt->nChannels; + + need_remap = FALSE; + } + + TRACE("need_remapping: %u, alsa_channels: %d\n", need_remap, *alsa_channels); + + return need_remap ? S_OK : S_FALSE; +} + +static NTSTATUS is_format_supported(void *args) +{ + struct is_format_supported_params *params = args; + const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)params->fmt_in; + snd_pcm_t *pcm_handle; + snd_pcm_hw_params_t *hw_params; + snd_pcm_format_mask_t *formats = NULL; + snd_pcm_format_t format; + WAVEFORMATEXTENSIBLE *closest = NULL; + unsigned int max = 0, min = 0; + int err; + int alsa_channels, alsa_channel_map[32]; + + params->result = S_OK; + + if(!params->fmt_in || (params->share == AUDCLNT_SHAREMODE_SHARED && !params->fmt_out)) + params->result = E_POINTER; + else if(params->share != AUDCLNT_SHAREMODE_SHARED && params->share != AUDCLNT_SHAREMODE_EXCLUSIVE) + params->result = E_INVALIDARG; + else if(params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + if(params->fmt_in->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) + params->result = E_INVALIDARG; + else if(params->fmt_in->nAvgBytesPerSec == 0 || params->fmt_in->nBlockAlign == 0 || + (fmtex->Samples.wValidBitsPerSample > params->fmt_in->wBitsPerSample)) + params->result = E_INVALIDARG; + } + if(FAILED(params->result)) + return STATUS_SUCCESS; + + if(params->fmt_in->nChannels == 0){ + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + return STATUS_SUCCESS; + } + + params->result = alsa_open_device(params->alsa_name, params->flow, &pcm_handle, &hw_params); + if(FAILED(params->result)) + return STATUS_SUCCESS; + + if((err = snd_pcm_hw_params_any(pcm_handle, hw_params)) < 0){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + goto exit; + } + + formats = calloc(1, snd_pcm_format_mask_sizeof()); + if(!formats){ + params->result = E_OUTOFMEMORY; + goto exit; + } + + snd_pcm_hw_params_get_format_mask(hw_params, formats); + format = alsa_format(params->fmt_in); + if (format == SND_PCM_FORMAT_UNKNOWN || + !snd_pcm_format_mask_test(formats, format)){ + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + goto exit; + } + + closest = clone_format(params->fmt_in); + if(!closest){ + params->result = E_OUTOFMEMORY; + goto exit; + } + + if((err = snd_pcm_hw_params_get_rate_min(hw_params, &min, NULL)) < 0){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + WARN("Unable to get min rate: %d (%s)\n", err, snd_strerror(err)); + goto exit; + } + + if((err = snd_pcm_hw_params_get_rate_max(hw_params, &max, NULL)) < 0){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + WARN("Unable to get max rate: %d (%s)\n", err, snd_strerror(err)); + goto exit; + } + + if(params->fmt_in->nSamplesPerSec < min || params->fmt_in->nSamplesPerSec > max){ + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + goto exit; + } + + if((err = snd_pcm_hw_params_get_channels_min(hw_params, &min)) < 0){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + WARN("Unable to get min channels: %d (%s)\n", err, snd_strerror(err)); + goto exit; + } + + if((err = snd_pcm_hw_params_get_channels_max(hw_params, &max)) < 0){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + WARN("Unable to get max channels: %d (%s)\n", err, snd_strerror(err)); + goto exit; + } + if(params->fmt_in->nChannels > max){ + params->result = S_FALSE; + closest->Format.nChannels = max; + }else if(params->fmt_in->nChannels < min){ + params->result = S_FALSE; + closest->Format.nChannels = min; + } + + map_channels(params->flow, params->fmt_in, &alsa_channels, alsa_channel_map); + + if(alsa_channels > max){ + params->result = S_FALSE; + closest->Format.nChannels = max; + } + + if(closest->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + closest->dwChannelMask = get_channel_mask(closest->Format.nChannels); + + if(params->fmt_in->nBlockAlign != params->fmt_in->nChannels * params->fmt_in->wBitsPerSample / 8 || + params->fmt_in->nAvgBytesPerSec != params->fmt_in->nBlockAlign * params->fmt_in->nSamplesPerSec || + (params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + fmtex->Samples.wValidBitsPerSample < params->fmt_in->wBitsPerSample)) + params->result = S_FALSE; + + if(params->share == AUDCLNT_SHAREMODE_EXCLUSIVE && params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + if(fmtex->dwChannelMask == 0 || fmtex->dwChannelMask & SPEAKER_RESERVED) + params->result = S_FALSE; + } + +exit: + if(params->result == S_FALSE && !params->fmt_out) + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + + if(params->result == S_FALSE && params->fmt_out) { + closest->Format.nBlockAlign = closest->Format.nChannels * closest->Format.wBitsPerSample / 8; + closest->Format.nAvgBytesPerSec = closest->Format.nBlockAlign * closest->Format.nSamplesPerSec; + if(closest->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + closest->Samples.wValidBitsPerSample = closest->Format.wBitsPerSample; + memcpy(params->fmt_out, closest, closest->Format.cbSize); + } + free(closest); + free(formats); + free(hw_params); + snd_pcm_close(pcm_handle); + + return STATUS_SUCCESS; +} + static NTSTATUS get_mix_format(void *args) { struct get_mix_format_params *params = args; @@ -632,5 +922,6 @@ exit: unixlib_entry_t __wine_unix_call_funcs[] = { get_endpoint_ids, + is_format_supported, get_mix_format, }; diff --git a/dlls/winealsa.drv/mmdevdrv.c b/dlls/winealsa.drv/mmdevdrv.c index 24c6755d697..8a80acd871a 100644 --- a/dlls/winealsa.drv/mmdevdrv.c +++ b/dlls/winealsa.drv/mmdevdrv.c @@ -1296,150 +1296,30 @@ static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient3 *iface, WAVEFORMATEX **out) { ACImpl *This = impl_from_IAudioClient3(iface); - snd_pcm_format_mask_t *formats = NULL; - snd_pcm_format_t format; - HRESULT hr = S_OK; - WAVEFORMATEX *closest = NULL; - unsigned int max = 0, min = 0; - int err; - int alsa_channels, alsa_channel_map[32]; + struct is_format_supported_params params; TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out); + if(fmt) dump_fmt(fmt); - if(!fmt || (mode == AUDCLNT_SHAREMODE_SHARED && !out)) - return E_POINTER; - - if(mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE) - return E_INVALIDARG; - - if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && - fmt->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) - return E_INVALIDARG; - - dump_fmt(fmt); + params.alsa_name = This->alsa_name; + params.flow = This->dataflow; + params.share = mode; + params.fmt_in = fmt; + params.fmt_out = NULL; if(out){ *out = NULL; - if(mode != AUDCLNT_SHAREMODE_SHARED) - out = NULL; + if(mode == AUDCLNT_SHAREMODE_SHARED) + params.fmt_out = CoTaskMemAlloc(sizeof(*params.fmt_out)); } + ALSA_CALL(is_format_supported, ¶ms); - if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && - (fmt->nAvgBytesPerSec == 0 || - fmt->nBlockAlign == 0 || - ((WAVEFORMATEXTENSIBLE*)fmt)->Samples.wValidBitsPerSample > fmt->wBitsPerSample)) - return E_INVALIDARG; + if(params.result == S_FALSE) + *out = ¶ms.fmt_out->Format; + else + CoTaskMemFree(params.fmt_out); - if(fmt->nChannels == 0) - return AUDCLNT_E_UNSUPPORTED_FORMAT; - - EnterCriticalSection(&This->lock); - - if((err = snd_pcm_hw_params_any(This->stream->pcm_handle, This->stream->hw_params)) < 0){ - hr = AUDCLNT_E_DEVICE_INVALIDATED; - goto exit; - } - - formats = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, - snd_pcm_format_mask_sizeof()); - if(!formats){ - hr = E_OUTOFMEMORY; - goto exit; - } - - snd_pcm_hw_params_get_format_mask(This->stream->hw_params, formats); - format = alsa_format(fmt); - if (format == SND_PCM_FORMAT_UNKNOWN || - !snd_pcm_format_mask_test(formats, format)){ - hr = AUDCLNT_E_UNSUPPORTED_FORMAT; - goto exit; - } - - closest = clone_format(fmt); - if(!closest){ - hr = E_OUTOFMEMORY; - goto exit; - } - - if((err = snd_pcm_hw_params_get_rate_min(This->stream->hw_params, &min, NULL)) < 0){ - hr = AUDCLNT_E_DEVICE_INVALIDATED; - WARN("Unable to get min rate: %d (%s)\n", err, snd_strerror(err)); - goto exit; - } - - if((err = snd_pcm_hw_params_get_rate_max(This->stream->hw_params, &max, NULL)) < 0){ - hr = AUDCLNT_E_DEVICE_INVALIDATED; - WARN("Unable to get max rate: %d (%s)\n", err, snd_strerror(err)); - goto exit; - } - - if(fmt->nSamplesPerSec < min || fmt->nSamplesPerSec > max){ - hr = AUDCLNT_E_UNSUPPORTED_FORMAT; - goto exit; - } - - if((err = snd_pcm_hw_params_get_channels_min(This->stream->hw_params, &min)) < 0){ - hr = AUDCLNT_E_DEVICE_INVALIDATED; - WARN("Unable to get min channels: %d (%s)\n", err, snd_strerror(err)); - goto exit; - } - - if((err = snd_pcm_hw_params_get_channels_max(This->stream->hw_params, &max)) < 0){ - hr = AUDCLNT_E_DEVICE_INVALIDATED; - WARN("Unable to get max channels: %d (%s)\n", err, snd_strerror(err)); - goto exit; - } - if(fmt->nChannels > max){ - hr = S_FALSE; - closest->nChannels = max; - }else if(fmt->nChannels < min){ - hr = S_FALSE; - closest->nChannels = min; - } - - map_channels(This, fmt, &alsa_channels, alsa_channel_map); - - if(alsa_channels > max){ - hr = S_FALSE; - closest->nChannels = max; - } - - if(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE) - ((WAVEFORMATEXTENSIBLE*)closest)->dwChannelMask = get_channel_mask(closest->nChannels); - - if(fmt->nBlockAlign != fmt->nChannels * fmt->wBitsPerSample / 8 || - fmt->nAvgBytesPerSec != fmt->nBlockAlign * fmt->nSamplesPerSec || - (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && - ((WAVEFORMATEXTENSIBLE*)fmt)->Samples.wValidBitsPerSample < fmt->wBitsPerSample)) - hr = S_FALSE; - - if(mode == AUDCLNT_SHAREMODE_EXCLUSIVE && - fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ - if(((WAVEFORMATEXTENSIBLE*)fmt)->dwChannelMask == 0 || - ((WAVEFORMATEXTENSIBLE*)fmt)->dwChannelMask & SPEAKER_RESERVED) - hr = S_FALSE; - } - -exit: - LeaveCriticalSection(&This->lock); - HeapFree(GetProcessHeap(), 0, formats); - - if(hr == S_FALSE && !out) - hr = AUDCLNT_E_UNSUPPORTED_FORMAT; - - if(hr == S_FALSE && out) { - closest->nBlockAlign = - closest->nChannels * closest->wBitsPerSample / 8; - closest->nAvgBytesPerSec = - closest->nBlockAlign * closest->nSamplesPerSec; - if(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE) - ((WAVEFORMATEXTENSIBLE*)closest)->Samples.wValidBitsPerSample = closest->wBitsPerSample; - *out = closest; - } else - CoTaskMemFree(closest); - - TRACE("returning: %08x\n", hr); - return hr; + return params.result; } static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, diff --git a/dlls/winealsa.drv/unixlib.h b/dlls/winealsa.drv/unixlib.h index ef5b470e1ed..cd8d303b5d3 100644 --- a/dlls/winealsa.drv/unixlib.h +++ b/dlls/winealsa.drv/unixlib.h @@ -67,6 +67,16 @@ struct get_endpoint_ids_params unsigned int default_idx; }; +struct is_format_supported_params +{ + const char *alsa_name; + EDataFlow flow; + AUDCLNT_SHAREMODE share; + const WAVEFORMATEX *fmt_in; + WAVEFORMATEXTENSIBLE *fmt_out; + HRESULT result; +}; + struct get_mix_format_params { const char *alsa_name; @@ -78,6 +88,7 @@ struct get_mix_format_params enum alsa_funcs { alsa_get_endpoint_ids, + alsa_is_format_supported, alsa_get_mix_format, };