diff --git a/dlls/winealsa.drv/alsa.c b/dlls/winealsa.drv/alsa.c index c199a2e603d..085066622c0 100644 --- a/dlls/winealsa.drv/alsa.c +++ b/dlls/winealsa.drv/alsa.c @@ -34,13 +34,13 @@ #include "windef.h" #include "winbase.h" #include "winternl.h" +#include "initguid.h" #include "mmdeviceapi.h" #include "wine/debug.h" #include "wine/list.h" #include "wine/unixlib.h" -#include "initguid.h" #include "unixlib.h" WINE_DEFAULT_DEBUG_CHANNEL(alsa); @@ -2241,6 +2241,184 @@ static NTSTATUS is_started(void *args) return alsa_unlock_result(stream, ¶ms->result, stream->started ? S_OK : S_FALSE); } +static unsigned int alsa_probe_num_speakers(char *name) +{ + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + int err; + unsigned int max_channels = 0; + + if ((err = snd_pcm_open(&handle, name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + WARN("The device \"%s\" failed to open: %d (%s).\n", + name, err, snd_strerror(err)); + return 0; + } + + params = malloc(snd_pcm_hw_params_sizeof()); + if (!params) { + WARN("Out of memory.\n"); + snd_pcm_close(handle); + return 0; + } + + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + WARN("snd_pcm_hw_params_any failed for \"%s\": %d (%s).\n", + name, err, snd_strerror(err)); + goto exit; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, + &max_channels)) < 0){ + WARN("Unable to get max channels: %d (%s)\n", err, snd_strerror(err)); + goto exit; + } + +exit: + free(params); + snd_pcm_close(handle); + + return max_channels; +} + +enum AudioDeviceConnectionType { + AudioDeviceConnectionType_Unknown = 0, + AudioDeviceConnectionType_PCI, + AudioDeviceConnectionType_USB +}; + +static NTSTATUS get_prop_value(void *args) +{ + struct get_prop_value_params *params = args; + const char *name = params->alsa_name; + EDataFlow flow = params->flow; + const GUID *guid = params->guid; + const PROPERTYKEY *prop = params->prop; + PROPVARIANT *out = params->value; + static const PROPERTYKEY devicepath_key = { /* undocumented? - {b3f8fa53-0004-438e-9003-51a46e139bfc},2 */ + {0xb3f8fa53, 0x0004, 0x438e, {0x90, 0x03, 0x51, 0xa4, 0x6e, 0x13, 0x9b, 0xfc}}, 2 + }; + + if(IsEqualPropertyKey(*prop, devicepath_key)) + { + char uevent[MAX_PATH]; + FILE *fuevent; + int card, device; + + /* only implemented for identifiable devices, i.e. not "default" */ + if(!sscanf(name, "plughw:%u,%u", &card, &device)){ + params->result = E_NOTIMPL; + return STATUS_SUCCESS; + } + sprintf(uevent, "/sys/class/sound/card%u/device/uevent", card); + fuevent = fopen(uevent, "r"); + + if(fuevent){ + enum AudioDeviceConnectionType connection = AudioDeviceConnectionType_Unknown; + USHORT vendor_id = 0, product_id = 0; + char line[256]; + + while (fgets(line, sizeof(line), fuevent)) { + char *val; + size_t val_len; + + if((val = strchr(line, '='))) { + val[0] = 0; + val++; + + val_len = strlen(val); + if(val_len > 0 && val[val_len - 1] == '\n') { val[val_len - 1] = 0; } + + if(!strcmp(line, "PCI_ID")){ + connection = AudioDeviceConnectionType_PCI; + if(sscanf(val, "%hX:%hX", &vendor_id, &product_id)<2){ + WARN("Unexpected input when reading PCI_ID in uevent file.\n"); + connection = AudioDeviceConnectionType_Unknown; + break; + } + }else if(!strcmp(line, "DEVTYPE") && !strcmp(val,"usb_interface")) + connection = AudioDeviceConnectionType_USB; + else if(!strcmp(line, "PRODUCT")) + if(sscanf(val, "%hx/%hx/", &vendor_id, &product_id)<2){ + WARN("Unexpected input when reading PRODUCT in uevent file.\n"); + connection = AudioDeviceConnectionType_Unknown; + break; + } + } + } + + fclose(fuevent); + + if(connection == AudioDeviceConnectionType_USB || connection == AudioDeviceConnectionType_PCI){ + UINT serial_number; + char buf[128]; + int len; + + /* As hardly any audio devices have serial numbers, Windows instead + appears to use a persistent random number. We emulate this here + by instead using the last 8 hex digits of the GUID. */ + serial_number = (guid->Data4[4] << 24) | (guid->Data4[5] << 16) | (guid->Data4[6] << 8) | guid->Data4[7]; + + if(connection == AudioDeviceConnectionType_USB) + sprintf(buf, "{1}.USB\\VID_%04X&PID_%04X\\%u&%08X", + vendor_id, product_id, device, serial_number); + else /* AudioDeviceConnectionType_PCI */ + sprintf(buf, "{1}.HDAUDIO\\FUNC_01&VEN_%04X&DEV_%04X\\%u&%08X", + vendor_id, product_id, device, serial_number); + + len = strlen(buf) + 1; + if(*params->buffer_size < len * sizeof(WCHAR)){ + params->result = E_NOT_SUFFICIENT_BUFFER; + *params->buffer_size = len * sizeof(WCHAR); + return STATUS_SUCCESS; + } + out->vt = VT_LPWSTR; + out->pwszVal = params->buffer; + ntdll_umbstowcs(buf, len, out->pwszVal, len); + params->result = S_OK; + return STATUS_SUCCESS; + } + }else{ + WARN("Could not open %s for reading\n", uevent); + params->result = E_NOTIMPL; + return STATUS_SUCCESS; + } + } else if (flow != eCapture && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { + unsigned int num_speakers, card, device; + char hwname[255]; + + if (sscanf(name, "plughw:%u,%u", &card, &device)) + sprintf(hwname, "hw:%u,%u", card, device); /* must be hw rather than plughw to work */ + else + strcpy(hwname, name); + + num_speakers = alsa_probe_num_speakers(hwname); + if (num_speakers == 0){ + params->result = E_FAIL; + return STATUS_SUCCESS; + } + out->vt = VT_UI4; + + if (num_speakers > 6) + out->ulVal = KSAUDIO_SPEAKER_STEREO; + else if (num_speakers == 6) + out->ulVal = KSAUDIO_SPEAKER_5POINT1; + else if (num_speakers >= 4) + out->ulVal = KSAUDIO_SPEAKER_QUAD; + else if (num_speakers >= 2) + out->ulVal = KSAUDIO_SPEAKER_STEREO; + else if (num_speakers == 1) + out->ulVal = KSAUDIO_SPEAKER_MONO; + + params->result = S_OK; + return STATUS_SUCCESS; + } + + TRACE("Unimplemented property %s,%u\n", wine_dbgstr_guid(&prop->fmtid), prop->pid); + + params->result = E_NOTIMPL; + return STATUS_SUCCESS; +} + unixlib_entry_t __wine_unix_call_funcs[] = { get_endpoint_ids, @@ -2265,4 +2443,5 @@ unixlib_entry_t __wine_unix_call_funcs[] = set_volumes, set_event_handle, is_started, + get_prop_value, }; diff --git a/dlls/winealsa.drv/mmdevdrv.c b/dlls/winealsa.drv/mmdevdrv.c index c2ec3dd54af..389c40b47de 100644 --- a/dlls/winealsa.drv/mmdevdrv.c +++ b/dlls/winealsa.drv/mmdevdrv.c @@ -49,8 +49,6 @@ #include "audioclient.h" #include "audiopolicy.h" -#include - #include "unixlib.h" WINE_DEFAULT_DEBUG_CHANNEL(alsa); @@ -2444,58 +2442,12 @@ HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device, return S_OK; } -static unsigned int alsa_probe_num_speakers(char *name) { - snd_pcm_t *handle; - snd_pcm_hw_params_t *params; - int err; - unsigned int max_channels = 0; - - if ((err = snd_pcm_open(&handle, name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { - WARN("The device \"%s\" failed to open: %d (%s).\n", - name, err, snd_strerror(err)); - return 0; - } - - params = HeapAlloc(GetProcessHeap(), 0, snd_pcm_hw_params_sizeof()); - if (!params) { - WARN("Out of memory.\n"); - snd_pcm_close(handle); - return 0; - } - - if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { - WARN("snd_pcm_hw_params_any failed for \"%s\": %d (%s).\n", - name, err, snd_strerror(err)); - goto exit; - } - - if ((err = snd_pcm_hw_params_get_channels_max(params, - &max_channels)) < 0){ - WARN("Unable to get max channels: %d (%s)\n", err, snd_strerror(err)); - goto exit; - } - -exit: - HeapFree(GetProcessHeap(), 0, params); - snd_pcm_close(handle); - - return max_channels; -} - -enum AudioDeviceConnectionType { - AudioDeviceConnectionType_Unknown = 0, - AudioDeviceConnectionType_PCI, - AudioDeviceConnectionType_USB -}; - HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARIANT *out) { + struct get_prop_value_params params; char name[256]; EDataFlow flow; - - static const PROPERTYKEY devicepath_key = { /* undocumented? - {b3f8fa53-0004-438e-9003-51a46e139bfc},2 */ - {0xb3f8fa53, 0x0004, 0x438e, {0x90, 0x03, 0x51, 0xa4, 0x6e, 0x13, 0x9b, 0xfc}}, 2 - }; + unsigned int size = 0; TRACE("%s, (%s,%u), %p\n", wine_dbgstr_guid(guid), wine_dbgstr_guid(&prop->fmtid), prop->pid, out); @@ -2505,116 +2457,27 @@ HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARI return E_NOINTERFACE; } - if(IsEqualPropertyKey(*prop, devicepath_key)) - { - char uevent[MAX_PATH]; - FILE *fuevent; - int card, device; + params.alsa_name = name; + params.flow = flow; + params.guid = guid; + params.prop = prop; + params.value = out; + params.buffer = NULL; + params.buffer_size = &size; - /* only implemented for identifiable devices, i.e. not "default" */ - if(!sscanf(name, "plughw:%u,%u", &card, &device)) - return E_NOTIMPL; + while(1) { + ALSA_CALL(get_prop_value, ¶ms); - sprintf(uevent, "/sys/class/sound/card%u/device/uevent", card); - fuevent = fopen(uevent, "r"); + if(params.result != E_NOT_SUFFICIENT_BUFFER) + break; - if(fuevent){ - enum AudioDeviceConnectionType connection = AudioDeviceConnectionType_Unknown; - USHORT vendor_id = 0, product_id = 0; - char line[256]; - - while (fgets(line, sizeof(line), fuevent)) { - char *val; - size_t val_len; - - if((val = strchr(line, '='))) { - val[0] = 0; - val++; - - val_len = strlen(val); - if(val_len > 0 && val[val_len - 1] == '\n') { val[val_len - 1] = 0; } - - if(!strcmp(line, "PCI_ID")){ - connection = AudioDeviceConnectionType_PCI; - if(sscanf(val, "%hX:%hX", &vendor_id, &product_id)<2){ - WARN("Unexpected input when reading PCI_ID in uevent file.\n"); - connection = AudioDeviceConnectionType_Unknown; - break; - } - }else if(!strcmp(line, "DEVTYPE") && !strcmp(val,"usb_interface")) - connection = AudioDeviceConnectionType_USB; - else if(!strcmp(line, "PRODUCT")) - if(sscanf(val, "%hx/%hx/", &vendor_id, &product_id)<2){ - WARN("Unexpected input when reading PRODUCT in uevent file.\n"); - connection = AudioDeviceConnectionType_Unknown; - break; - } - } - } - - fclose(fuevent); - - if(connection == AudioDeviceConnectionType_USB || connection == AudioDeviceConnectionType_PCI){ - static const WCHAR usbformatW[] = { '{','1','}','.','U','S','B','\\','V','I','D','_', - '%','0','4','X','&','P','I','D','_','%','0','4','X','\\', - '%','u','&','%','0','8','X',0 }; /* "{1}.USB\VID_%04X&PID_%04X\%u&%08X" */ - static const WCHAR pciformatW[] = { '{','1','}','.','H','D','A','U','D','I','O','\\','F','U','N','C','_','0','1','&', - 'V','E','N','_','%','0','4','X','&','D','E','V','_', - '%','0','4','X','\\','%','u','&','%','0','8','X',0 }; /* "{1}.HDAUDIO\FUNC_01&VEN_%04X&DEV_%04X\%u&%08X" */ - UINT serial_number; - - /* As hardly any audio devices have serial numbers, Windows instead - appears to use a persistent random number. We emulate this here - by instead using the last 8 hex digits of the GUID. */ - serial_number = (guid->Data4[4] << 24) | (guid->Data4[5] << 16) | (guid->Data4[6] << 8) | guid->Data4[7]; - - out->vt = VT_LPWSTR; - out->pwszVal = CoTaskMemAlloc(128 * sizeof(WCHAR)); - - if(!out->pwszVal) - return E_OUTOFMEMORY; - - if(connection == AudioDeviceConnectionType_USB) - sprintfW( out->pwszVal, usbformatW, vendor_id, product_id, device, serial_number); - else if(connection == AudioDeviceConnectionType_PCI) - sprintfW( out->pwszVal, pciformatW, vendor_id, product_id, device, serial_number); - - return S_OK; - } - }else{ - WARN("Could not open %s for reading\n", uevent); - return E_NOTIMPL; - } - } else if (flow != eCapture && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { - unsigned int num_speakers, card, device; - char hwname[255]; - - if (sscanf(name, "plughw:%u,%u", &card, &device)) - sprintf(hwname, "hw:%u,%u", card, device); /* must be hw rather than plughw to work */ - else - strcpy(hwname, name); - - num_speakers = alsa_probe_num_speakers(hwname); - if (num_speakers == 0) - return E_FAIL; - - out->vt = VT_UI4; - - if (num_speakers > 6) - out->ulVal = KSAUDIO_SPEAKER_STEREO; - else if (num_speakers == 6) - out->ulVal = KSAUDIO_SPEAKER_5POINT1; - else if (num_speakers >= 4) - out->ulVal = KSAUDIO_SPEAKER_QUAD; - else if (num_speakers >= 2) - out->ulVal = KSAUDIO_SPEAKER_STEREO; - else if (num_speakers == 1) - out->ulVal = KSAUDIO_SPEAKER_MONO; - - return S_OK; + CoTaskMemFree(params.buffer); + params.buffer = CoTaskMemAlloc(*params.buffer_size); + if(!params.buffer) + return E_OUTOFMEMORY; } + if(FAILED(params.result)) + CoTaskMemFree(params.buffer); - TRACE("Unimplemented property %s,%u\n", wine_dbgstr_guid(&prop->fmtid), prop->pid); - - return E_NOTIMPL; + return params.result; } diff --git a/dlls/winealsa.drv/unixlib.h b/dlls/winealsa.drv/unixlib.h index 4326f79a50b..c2de48cef65 100644 --- a/dlls/winealsa.drv/unixlib.h +++ b/dlls/winealsa.drv/unixlib.h @@ -195,6 +195,18 @@ struct is_started_params HRESULT result; }; +struct get_prop_value_params +{ + const char *alsa_name; + EDataFlow flow; + const GUID *guid; + const PROPERTYKEY *prop; + HRESULT result; + PROPVARIANT *value; + void *buffer; /* caller allocated buffer to hold value's strings */ + unsigned int *buffer_size; +}; + enum alsa_funcs { alsa_get_endpoint_ids, @@ -219,6 +231,7 @@ enum alsa_funcs alsa_set_volumes, alsa_set_event_handle, alsa_is_started, + alsa_get_prop_value, }; extern unixlib_handle_t alsa_handle;