diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 9eb056ab506..59806f28465 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -72,8 +72,7 @@ enum DriverPriority { Priority_Preferred }; -static const REFERENCE_TIME MinimumPeriod = 30000; -static const REFERENCE_TIME DefaultPeriod = 100000; +static struct pulse_config pulse_config; static pa_context *pulse_ctx; static pa_mainloop *pulse_ml; @@ -81,12 +80,6 @@ static pa_mainloop *pulse_ml; static HANDLE pulse_thread; static struct list g_sessions = LIST_INIT(g_sessions); -static UINT g_phys_speakers_mask = 0; - -/* Mixer format + period times */ -static WAVEFORMATEXTENSIBLE pulse_fmt[2]; -static REFERENCE_TIME pulse_min_period[2], pulse_def_period[2]; - static GUID pulse_render_guid = { 0xfd47d9cc, 0x4218, 0x4135, { 0x9c, 0xe2, 0x0c, 0x19, 0x5c, 0x87, 0x40, 0x5b } }; static GUID pulse_capture_guid = @@ -340,198 +333,23 @@ 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 char *get_application_name(void) { - int i; - DWORD mask = 0; + WCHAR path[MAX_PATH], *name; + size_t len; + char *str; - 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; - case PA_CHANNEL_POSITION_MONO: - case PA_CHANNEL_POSITION_FRONT_CENTER: mask |= SPEAKER_FRONT_CENTER; break; - case PA_CHANNEL_POSITION_FRONT_RIGHT: mask |= SPEAKER_FRONT_RIGHT; break; - case PA_CHANNEL_POSITION_REAR_LEFT: mask |= SPEAKER_BACK_LEFT; break; - case PA_CHANNEL_POSITION_REAR_CENTER: mask |= SPEAKER_BACK_CENTER; break; - case PA_CHANNEL_POSITION_REAR_RIGHT: mask |= SPEAKER_BACK_RIGHT; break; - case PA_CHANNEL_POSITION_LFE: mask |= SPEAKER_LOW_FREQUENCY; break; - case PA_CHANNEL_POSITION_SIDE_LEFT: mask |= SPEAKER_SIDE_LEFT; break; - case PA_CHANNEL_POSITION_SIDE_RIGHT: mask |= SPEAKER_SIDE_RIGHT; break; - case PA_CHANNEL_POSITION_TOP_CENTER: mask |= SPEAKER_TOP_CENTER; break; - case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: mask |= SPEAKER_TOP_FRONT_LEFT; break; - case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: mask |= SPEAKER_TOP_FRONT_CENTER; break; - case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: mask |= SPEAKER_TOP_FRONT_RIGHT; break; - case PA_CHANNEL_POSITION_TOP_REAR_LEFT: mask |= SPEAKER_TOP_BACK_LEFT; break; - case PA_CHANNEL_POSITION_TOP_REAR_CENTER: mask |= SPEAKER_TOP_BACK_CENTER; break; - 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; - pa_channel_map map; - pa_sample_spec ss; - pa_buffer_attr attr; - int ret; - unsigned int length = 0; - - pa_channel_map_init_auto(&map, 2, PA_CHANNEL_MAP_ALSA); - ss.rate = 48000; - ss.format = PA_SAMPLE_FLOAT32LE; - ss.channels = map.channels; - - attr.maxlength = -1; - attr.tlength = -1; - attr.minreq = attr.fragsize = pa_frame_size(&ss); - attr.prebuf = 0; - - stream = pa_stream_new(pulse_ctx, "format test stream", &ss, &map); - if (stream) - pa_stream_set_state_callback(stream, pulse_stream_state, NULL); - if (!stream) - ret = -1; - else if (render) - ret = pa_stream_connect_playback(stream, NULL, &attr, - PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS, NULL, NULL); + GetModuleFileNameW(NULL, path, ARRAY_SIZE(path)); + name = strrchrW(path, '\\'); + if (!name) + name = path; else - ret = pa_stream_connect_record(stream, NULL, &attr, PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS); - if (ret >= 0) { - while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && - pa_stream_get_state(stream) == PA_STREAM_CREATING) - {} - if (pa_stream_get_state(stream) == PA_STREAM_READY) { - ss = *pa_stream_get_sample_spec(stream); - map = *pa_stream_get_channel_map(stream); - if (render) - length = pa_stream_get_buffer_attr(stream)->minreq; - else - length = pa_stream_get_buffer_attr(stream)->fragsize; - pa_stream_disconnect(stream); - while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && - pa_stream_get_state(stream) == PA_STREAM_READY) - {} - } - } - - if (stream) - pa_stream_unref(stream); - - if (length) - pulse_def_period[!render] = pulse_min_period[!render] = pa_bytes_to_usec(10 * length, &ss); - - if (pulse_min_period[!render] < MinimumPeriod) - pulse_min_period[!render] = MinimumPeriod; - - if (pulse_def_period[!render] < DefaultPeriod) - pulse_def_period[!render] = DefaultPeriod; - - wfx->wFormatTag = WAVE_FORMAT_EXTENSIBLE; - wfx->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - - convert_channel_map(&map, fmt); - - wfx->wBitsPerSample = 8 * pa_sample_size_of_format(ss.format); - wfx->nSamplesPerSec = ss.rate; - 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; - else - fmt->Samples.wValidBitsPerSample = 24; - if (ss.format == PA_SAMPLE_FLOAT32LE) - fmt->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - else - fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + name++; + len = WideCharToMultiByte(CP_UTF8, 0, name, -1, NULL, 0, NULL, NULL); + if (!(str = malloc(len))) + return NULL; + WideCharToMultiByte(CP_UNIXCP, 0, name, -1, str, len, NULL, NULL); + return str; } static HRESULT pulse_connect(void) @@ -601,100 +419,6 @@ fail: return E_FAIL; } -/* For default PulseAudio render device, OR together all of the - * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ -static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) -{ - if (i) - g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map); -} - -/* some poorly-behaved applications call audio functions during DllMain, so we - * have to do as much as possible without creating a new thread. this function - * sets up a synchronous connection to verify the server is running and query - * static data. */ -static HRESULT pulse_test_connect(void) -{ - int len, ret; - WCHAR path[MAX_PATH], *name; - char *str; - pa_operation *o; - - pulse_ml = pa_mainloop_new(); - - pa_mainloop_set_poll_func(pulse_ml, pulse_poll_func, NULL); - - GetModuleFileNameW(NULL, path, ARRAY_SIZE(path)); - name = strrchrW(path, '\\'); - if (!name) - name = path; - else - name++; - len = WideCharToMultiByte(CP_UNIXCP, 0, name, -1, NULL, 0, NULL, NULL); - str = pa_xmalloc(len); - WideCharToMultiByte(CP_UNIXCP, 0, name, -1, str, len, NULL, NULL); - TRACE("Name: %s\n", str); - pulse_ctx = pa_context_new(pa_mainloop_get_api(pulse_ml), str); - pa_xfree(str); - if (!pulse_ctx) { - ERR("Failed to create context\n"); - pa_mainloop_free(pulse_ml); - pulse_ml = NULL; - return E_FAIL; - } - - pa_context_set_state_callback(pulse_ctx, pulse_contextcallback, NULL); - - TRACE("libpulse protocol version: %u. API Version %u\n", pa_context_get_protocol_version(pulse_ctx), PA_API_VERSION); - if (pa_context_connect(pulse_ctx, NULL, 0, NULL) < 0) - goto fail; - - /* Wait for connection */ - while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0) { - pa_context_state_t state = pa_context_get_state(pulse_ctx); - - if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) - goto fail; - - if (state == PA_CONTEXT_READY) - break; - } - - if (pa_context_get_state(pulse_ctx) != PA_CONTEXT_READY) - goto fail; - - TRACE("Test-connected to server %s with protocol version: %i.\n", - pa_context_get_server(pulse_ctx), - pa_context_get_server_protocol_version(pulse_ctx)); - - pulse_probe_settings(1, &pulse_fmt[0]); - pulse_probe_settings(0, &pulse_fmt[1]); - - g_phys_speakers_mask = 0; - o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); - if (o) { - while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && - pa_operation_get_state(o) == PA_OPERATION_RUNNING) - {} - pa_operation_unref(o); - } - - pa_context_unref(pulse_ctx); - pulse_ctx = NULL; - pa_mainloop_free(pulse_ml); - pulse_ml = NULL; - - return S_OK; - -fail: - pa_context_unref(pulse_ctx); - pulse_ctx = NULL; - pa_mainloop_free(pulse_ml); - pulse_ml = NULL; - - return E_FAIL; -} - static HRESULT pulse_stream_valid(ACImpl *This) { if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; @@ -1196,10 +920,12 @@ HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID ** int WINAPI AUDDRV_GetPriority(void) { + char *name; HRESULT hr; - pulse->lock(); - hr = pulse_test_connect(); - pulse->unlock(); + + name = get_application_name(); + hr = pulse->test_connect(name, &pulse_config); + free(name); return SUCCEEDED(hr) ? Priority_Preferred : Priority_Unavailable; } @@ -1640,7 +1366,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, if (FAILED(hr)) goto exit; - period = pulse_def_period[This->dataflow == eCapture]; + period = pulse_config.modes[This->dataflow == eCapture].def_period; if (duration < 3 * period) duration = 3 * period; @@ -1757,7 +1483,7 @@ static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient3 *iface, *latency = 10000000; *latency *= lat; *latency /= This->ss.rate; - *latency += pulse_def_period[0]; + *latency += pulse_config.modes[0].def_period; pulse->unlock(); TRACE("Latency: %u ms\n", (DWORD)(*latency / 10000)); return S_OK; @@ -1965,14 +1691,13 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, WAVEFORMATEX **pwfx) { ACImpl *This = impl_from_IAudioClient3(iface); - WAVEFORMATEXTENSIBLE *fmt = &pulse_fmt[This->dataflow == eCapture]; TRACE("(%p)->(%p)\n", This, pwfx); if (!pwfx) return E_POINTER; - *pwfx = clone_format(&fmt->Format); + *pwfx = clone_format(&pulse_config.modes[This->dataflow == eCapture].format.Format); if (!*pwfx) return E_OUTOFMEMORY; dump_fmt(*pwfx); @@ -1990,9 +1715,9 @@ static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface, return E_POINTER; if (defperiod) - *defperiod = pulse_def_period[This->dataflow == eCapture]; + *defperiod = pulse_config.modes[This->dataflow == eCapture].def_period; if (minperiod) - *minperiod = pulse_min_period[This->dataflow == eCapture]; + *minperiod = pulse_config.modes[This->dataflow == eCapture].min_period; return S_OK; } @@ -3699,7 +3424,7 @@ HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARI if (IsEqualGUID(guid, &pulse_render_guid) && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { out->vt = VT_UI4; - out->ulVal = g_phys_speakers_mask; + out->ulVal = pulse_config.speakers_mask; return out->ulVal ? S_OK : E_FAIL; } diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index f6f2a45264e..4b4d2497314 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -24,13 +24,35 @@ #include #include +#include + +#include #include "ntstatus.h" #define WIN32_NO_STATUS #include "winternl.h" +#include "initguid.h" +#include "audioclient.h" + #include "unixlib.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(pulse); + +static pa_context *pulse_ctx; +static pa_mainloop *pulse_ml; + +/* Mixer format + period times */ +static WAVEFORMATEXTENSIBLE pulse_fmt[2]; +static REFERENCE_TIME pulse_min_period[2], pulse_def_period[2]; + +static UINT g_phys_speakers_mask = 0; + +static const REFERENCE_TIME MinimumPeriod = 30000; +static const REFERENCE_TIME DefaultPeriod = 100000; + static pthread_mutex_t pulse_mutex; static pthread_cond_t pulse_cond = PTHREAD_COND_INITIALIZER; @@ -54,12 +76,343 @@ static void WINAPI pulse_broadcast(void) pthread_cond_broadcast(&pulse_cond); } +static int pulse_poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) +{ + int r; + pulse_unlock(); + r = poll(ufds, nfds, timeout); + pulse_lock(); + return r; +} + +static void pulse_contextcallback(pa_context *c, void *userdata) +{ + switch (pa_context_get_state(c)) { + default: + FIXME("Unhandled state: %i\n", pa_context_get_state(c)); + return; + + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + case PA_CONTEXT_TERMINATED: + TRACE("State change to %i\n", pa_context_get_state(c)); + return; + + case PA_CONTEXT_READY: + TRACE("Ready\n"); + break; + + case PA_CONTEXT_FAILED: + WARN("Context failed: %s\n", pa_strerror(pa_context_errno(c))); + break; + } + pulse_broadcast(); +} + +static void pulse_stream_state(pa_stream *s, void *user) +{ + pa_stream_state_t state = pa_stream_get_state(s); + TRACE("Stream state changed to %i\n", state); + pulse_broadcast(); +} + +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) { + 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; + case PA_CHANNEL_POSITION_MONO: + case PA_CHANNEL_POSITION_FRONT_CENTER: mask |= SPEAKER_FRONT_CENTER; break; + case PA_CHANNEL_POSITION_FRONT_RIGHT: mask |= SPEAKER_FRONT_RIGHT; break; + case PA_CHANNEL_POSITION_REAR_LEFT: mask |= SPEAKER_BACK_LEFT; break; + case PA_CHANNEL_POSITION_REAR_CENTER: mask |= SPEAKER_BACK_CENTER; break; + case PA_CHANNEL_POSITION_REAR_RIGHT: mask |= SPEAKER_BACK_RIGHT; break; + case PA_CHANNEL_POSITION_LFE: mask |= SPEAKER_LOW_FREQUENCY; break; + case PA_CHANNEL_POSITION_SIDE_LEFT: mask |= SPEAKER_SIDE_LEFT; break; + case PA_CHANNEL_POSITION_SIDE_RIGHT: mask |= SPEAKER_SIDE_RIGHT; break; + case PA_CHANNEL_POSITION_TOP_CENTER: mask |= SPEAKER_TOP_CENTER; break; + case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: mask |= SPEAKER_TOP_FRONT_LEFT; break; + case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: mask |= SPEAKER_TOP_FRONT_CENTER; break; + case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: mask |= SPEAKER_TOP_FRONT_RIGHT; break; + case PA_CHANNEL_POSITION_TOP_REAR_LEFT: mask |= SPEAKER_TOP_BACK_LEFT; break; + case PA_CHANNEL_POSITION_TOP_REAR_CENTER: mask |= SPEAKER_TOP_BACK_CENTER; break; + 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 default PulseAudio render device, OR together all of the + * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ +static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) +{ + if (i) + g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map); +} + +/* 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; + pa_channel_map map; + pa_sample_spec ss; + pa_buffer_attr attr; + int ret; + unsigned int length = 0; + + pa_channel_map_init_auto(&map, 2, PA_CHANNEL_MAP_ALSA); + ss.rate = 48000; + ss.format = PA_SAMPLE_FLOAT32LE; + ss.channels = map.channels; + + attr.maxlength = -1; + attr.tlength = -1; + attr.minreq = attr.fragsize = pa_frame_size(&ss); + attr.prebuf = 0; + + stream = pa_stream_new(pulse_ctx, "format test stream", &ss, &map); + if (stream) + pa_stream_set_state_callback(stream, pulse_stream_state, NULL); + if (!stream) + ret = -1; + else if (render) + ret = pa_stream_connect_playback(stream, NULL, &attr, + PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS, NULL, NULL); + else + ret = pa_stream_connect_record(stream, NULL, &attr, PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS); + if (ret >= 0) { + while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && + pa_stream_get_state(stream) == PA_STREAM_CREATING) + {} + if (pa_stream_get_state(stream) == PA_STREAM_READY) { + ss = *pa_stream_get_sample_spec(stream); + map = *pa_stream_get_channel_map(stream); + if (render) + length = pa_stream_get_buffer_attr(stream)->minreq; + else + length = pa_stream_get_buffer_attr(stream)->fragsize; + pa_stream_disconnect(stream); + while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && + pa_stream_get_state(stream) == PA_STREAM_READY) + {} + } + } + + if (stream) + pa_stream_unref(stream); + + if (length) + pulse_def_period[!render] = pulse_min_period[!render] = pa_bytes_to_usec(10 * length, &ss); + + if (pulse_min_period[!render] < MinimumPeriod) + pulse_min_period[!render] = MinimumPeriod; + + if (pulse_def_period[!render] < DefaultPeriod) + pulse_def_period[!render] = DefaultPeriod; + + wfx->wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wfx->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + + convert_channel_map(&map, fmt); + + wfx->wBitsPerSample = 8 * pa_sample_size_of_format(ss.format); + wfx->nSamplesPerSec = ss.rate; + 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; + else + fmt->Samples.wValidBitsPerSample = 24; + if (ss.format == PA_SAMPLE_FLOAT32LE) + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + else + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; +} + +/* some poorly-behaved applications call audio functions during DllMain, so we + * have to do as much as possible without creating a new thread. this function + * sets up a synchronous connection to verify the server is running and query + * static data. */ +static HRESULT WINAPI pulse_test_connect(const char *name, struct pulse_config *config) +{ + pa_operation *o; + int ret; + + pulse_lock(); + pulse_ml = pa_mainloop_new(); + + pa_mainloop_set_poll_func(pulse_ml, pulse_poll_func, NULL); + + pulse_ctx = pa_context_new(pa_mainloop_get_api(pulse_ml), name); + if (!pulse_ctx) { + ERR("Failed to create context\n"); + pa_mainloop_free(pulse_ml); + pulse_ml = NULL; + pulse_unlock(); + return E_FAIL; + } + + pa_context_set_state_callback(pulse_ctx, pulse_contextcallback, NULL); + + TRACE("libpulse protocol version: %u. API Version %u\n", pa_context_get_protocol_version(pulse_ctx), PA_API_VERSION); + if (pa_context_connect(pulse_ctx, NULL, 0, NULL) < 0) + goto fail; + + /* Wait for connection */ + while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0) { + pa_context_state_t state = pa_context_get_state(pulse_ctx); + + if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) + goto fail; + + if (state == PA_CONTEXT_READY) + break; + } + + if (pa_context_get_state(pulse_ctx) != PA_CONTEXT_READY) + goto fail; + + TRACE("Test-connected to server %s with protocol version: %i.\n", + pa_context_get_server(pulse_ctx), + pa_context_get_server_protocol_version(pulse_ctx)); + + pulse_probe_settings(1, &pulse_fmt[0]); + pulse_probe_settings(0, &pulse_fmt[1]); + + g_phys_speakers_mask = 0; + o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); + if (o) { + while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && + pa_operation_get_state(o) == PA_OPERATION_RUNNING) + {} + pa_operation_unref(o); + } + + pa_context_unref(pulse_ctx); + pulse_ctx = NULL; + pa_mainloop_free(pulse_ml); + pulse_ml = NULL; + + config->speakers_mask = g_phys_speakers_mask; + config->modes[0].format = pulse_fmt[0]; + config->modes[0].def_period = pulse_def_period[0]; + config->modes[0].min_period = pulse_min_period[0]; + config->modes[1].format = pulse_fmt[1]; + config->modes[1].def_period = pulse_def_period[1]; + config->modes[1].min_period = pulse_min_period[1]; + + pulse_unlock(); + + return S_OK; + +fail: + pa_context_unref(pulse_ctx); + pulse_ctx = NULL; + pa_mainloop_free(pulse_ml); + pulse_ml = NULL; + pulse_unlock(); + + return E_FAIL; +} + static const struct unix_funcs unix_funcs = { pulse_lock, pulse_unlock, pulse_cond_wait, pulse_broadcast, + pulse_test_connect, }; NTSTATUS CDECL __wine_init_unix_lib(HMODULE module, DWORD reason, const void *ptr_in, void *ptr_out) diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index 97595ed3cee..865a6f31ec6 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -16,10 +16,22 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +struct pulse_config +{ + struct + { + WAVEFORMATEXTENSIBLE format; + REFERENCE_TIME def_period; + REFERENCE_TIME min_period; + } modes[2]; + unsigned int speakers_mask; +}; + struct unix_funcs { void (WINAPI *lock)(void); void (WINAPI *unlock)(void); int (WINAPI *cond_wait)(void); void (WINAPI *broadcast)(void); + HRESULT (WINAPI *test_connect)(const char *name, struct pulse_config *config); };