winepulse: Move pulse_test_connect to unix lib.

Signed-off-by: Jacek Caban <jacek@codeweavers.com>
Signed-off-by: Andrew Eikum <aeikum@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Jacek Caban 2021-05-11 18:30:00 +02:00 committed by Alexandre Julliard
parent d3673fcb03
commit e264ec9c71
3 changed files with 391 additions and 301 deletions

View File

@ -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;
}

View File

@ -24,13 +24,35 @@
#include <stdarg.h>
#include <pthread.h>
#include <poll.h>
#include <pulse/pulseaudio.h>
#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)

View File

@ -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);
};