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:
parent
d3673fcb03
commit
e264ec9c71
|
@ -72,8 +72,7 @@ enum DriverPriority {
|
||||||
Priority_Preferred
|
Priority_Preferred
|
||||||
};
|
};
|
||||||
|
|
||||||
static const REFERENCE_TIME MinimumPeriod = 30000;
|
static struct pulse_config pulse_config;
|
||||||
static const REFERENCE_TIME DefaultPeriod = 100000;
|
|
||||||
|
|
||||||
static pa_context *pulse_ctx;
|
static pa_context *pulse_ctx;
|
||||||
static pa_mainloop *pulse_ml;
|
static pa_mainloop *pulse_ml;
|
||||||
|
@ -81,12 +80,6 @@ static pa_mainloop *pulse_ml;
|
||||||
static HANDLE pulse_thread;
|
static HANDLE pulse_thread;
|
||||||
static struct list g_sessions = LIST_INIT(g_sessions);
|
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 =
|
static GUID pulse_render_guid =
|
||||||
{ 0xfd47d9cc, 0x4218, 0x4135, { 0x9c, 0xe2, 0x0c, 0x19, 0x5c, 0x87, 0x40, 0x5b } };
|
{ 0xfd47d9cc, 0x4218, 0x4135, { 0x9c, 0xe2, 0x0c, 0x19, 0x5c, 0x87, 0x40, 0x5b } };
|
||||||
static GUID pulse_capture_guid =
|
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
|
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;
|
WCHAR path[MAX_PATH], *name;
|
||||||
DWORD mask = 0;
|
size_t len;
|
||||||
|
char *str;
|
||||||
|
|
||||||
for (i = 0; i < map->channels; ++i) {
|
GetModuleFileNameW(NULL, path, ARRAY_SIZE(path));
|
||||||
switch (map->map[i]) {
|
name = strrchrW(path, '\\');
|
||||||
default: FIXME("Unhandled channel %s\n", pa_channel_position_to_string(map->map[i])); break;
|
if (!name)
|
||||||
case PA_CHANNEL_POSITION_FRONT_LEFT: mask |= SPEAKER_FRONT_LEFT; break;
|
name = path;
|
||||||
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);
|
|
||||||
else
|
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);
|
name++;
|
||||||
if (ret >= 0) {
|
len = WideCharToMultiByte(CP_UTF8, 0, name, -1, NULL, 0, NULL, NULL);
|
||||||
while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 &&
|
if (!(str = malloc(len)))
|
||||||
pa_stream_get_state(stream) == PA_STREAM_CREATING)
|
return NULL;
|
||||||
{}
|
WideCharToMultiByte(CP_UNIXCP, 0, name, -1, str, len, NULL, NULL);
|
||||||
if (pa_stream_get_state(stream) == PA_STREAM_READY) {
|
return str;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static HRESULT pulse_connect(void)
|
static HRESULT pulse_connect(void)
|
||||||
|
@ -601,100 +419,6 @@ fail:
|
||||||
return E_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) {
|
static HRESULT pulse_stream_valid(ACImpl *This) {
|
||||||
if (!This->stream)
|
if (!This->stream)
|
||||||
return AUDCLNT_E_NOT_INITIALIZED;
|
return AUDCLNT_E_NOT_INITIALIZED;
|
||||||
|
@ -1196,10 +920,12 @@ HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **
|
||||||
|
|
||||||
int WINAPI AUDDRV_GetPriority(void)
|
int WINAPI AUDDRV_GetPriority(void)
|
||||||
{
|
{
|
||||||
|
char *name;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
pulse->lock();
|
|
||||||
hr = pulse_test_connect();
|
name = get_application_name();
|
||||||
pulse->unlock();
|
hr = pulse->test_connect(name, &pulse_config);
|
||||||
|
free(name);
|
||||||
return SUCCEEDED(hr) ? Priority_Preferred : Priority_Unavailable;
|
return SUCCEEDED(hr) ? Priority_Preferred : Priority_Unavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1640,7 +1366,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface,
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
period = pulse_def_period[This->dataflow == eCapture];
|
period = pulse_config.modes[This->dataflow == eCapture].def_period;
|
||||||
if (duration < 3 * period)
|
if (duration < 3 * period)
|
||||||
duration = 3 * period;
|
duration = 3 * period;
|
||||||
|
|
||||||
|
@ -1757,7 +1483,7 @@ static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient3 *iface,
|
||||||
*latency = 10000000;
|
*latency = 10000000;
|
||||||
*latency *= lat;
|
*latency *= lat;
|
||||||
*latency /= This->ss.rate;
|
*latency /= This->ss.rate;
|
||||||
*latency += pulse_def_period[0];
|
*latency += pulse_config.modes[0].def_period;
|
||||||
pulse->unlock();
|
pulse->unlock();
|
||||||
TRACE("Latency: %u ms\n", (DWORD)(*latency / 10000));
|
TRACE("Latency: %u ms\n", (DWORD)(*latency / 10000));
|
||||||
return S_OK;
|
return S_OK;
|
||||||
|
@ -1965,14 +1691,13 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface,
|
||||||
WAVEFORMATEX **pwfx)
|
WAVEFORMATEX **pwfx)
|
||||||
{
|
{
|
||||||
ACImpl *This = impl_from_IAudioClient3(iface);
|
ACImpl *This = impl_from_IAudioClient3(iface);
|
||||||
WAVEFORMATEXTENSIBLE *fmt = &pulse_fmt[This->dataflow == eCapture];
|
|
||||||
|
|
||||||
TRACE("(%p)->(%p)\n", This, pwfx);
|
TRACE("(%p)->(%p)\n", This, pwfx);
|
||||||
|
|
||||||
if (!pwfx)
|
if (!pwfx)
|
||||||
return E_POINTER;
|
return E_POINTER;
|
||||||
|
|
||||||
*pwfx = clone_format(&fmt->Format);
|
*pwfx = clone_format(&pulse_config.modes[This->dataflow == eCapture].format.Format);
|
||||||
if (!*pwfx)
|
if (!*pwfx)
|
||||||
return E_OUTOFMEMORY;
|
return E_OUTOFMEMORY;
|
||||||
dump_fmt(*pwfx);
|
dump_fmt(*pwfx);
|
||||||
|
@ -1990,9 +1715,9 @@ static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface,
|
||||||
return E_POINTER;
|
return E_POINTER;
|
||||||
|
|
||||||
if (defperiod)
|
if (defperiod)
|
||||||
*defperiod = pulse_def_period[This->dataflow == eCapture];
|
*defperiod = pulse_config.modes[This->dataflow == eCapture].def_period;
|
||||||
if (minperiod)
|
if (minperiod)
|
||||||
*minperiod = pulse_min_period[This->dataflow == eCapture];
|
*minperiod = pulse_config.modes[This->dataflow == eCapture].min_period;
|
||||||
|
|
||||||
return S_OK;
|
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)) {
|
if (IsEqualGUID(guid, &pulse_render_guid) && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) {
|
||||||
out->vt = VT_UI4;
|
out->vt = VT_UI4;
|
||||||
out->ulVal = g_phys_speakers_mask;
|
out->ulVal = pulse_config.speakers_mask;
|
||||||
|
|
||||||
return out->ulVal ? S_OK : E_FAIL;
|
return out->ulVal ? S_OK : E_FAIL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,13 +24,35 @@
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
#include <pulse/pulseaudio.h>
|
||||||
|
|
||||||
#include "ntstatus.h"
|
#include "ntstatus.h"
|
||||||
#define WIN32_NO_STATUS
|
#define WIN32_NO_STATUS
|
||||||
#include "winternl.h"
|
#include "winternl.h"
|
||||||
|
|
||||||
|
#include "initguid.h"
|
||||||
|
#include "audioclient.h"
|
||||||
|
|
||||||
#include "unixlib.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_mutex_t pulse_mutex;
|
||||||
static pthread_cond_t pulse_cond = PTHREAD_COND_INITIALIZER;
|
static pthread_cond_t pulse_cond = PTHREAD_COND_INITIALIZER;
|
||||||
|
|
||||||
|
@ -54,12 +76,343 @@ static void WINAPI pulse_broadcast(void)
|
||||||
pthread_cond_broadcast(&pulse_cond);
|
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 =
|
static const struct unix_funcs unix_funcs =
|
||||||
{
|
{
|
||||||
pulse_lock,
|
pulse_lock,
|
||||||
pulse_unlock,
|
pulse_unlock,
|
||||||
pulse_cond_wait,
|
pulse_cond_wait,
|
||||||
pulse_broadcast,
|
pulse_broadcast,
|
||||||
|
pulse_test_connect,
|
||||||
};
|
};
|
||||||
|
|
||||||
NTSTATUS CDECL __wine_init_unix_lib(HMODULE module, DWORD reason, const void *ptr_in, void *ptr_out)
|
NTSTATUS CDECL __wine_init_unix_lib(HMODULE module, DWORD reason, const void *ptr_in, void *ptr_out)
|
||||||
|
|
|
@ -16,10 +16,22 @@
|
||||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
* 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
|
struct unix_funcs
|
||||||
{
|
{
|
||||||
void (WINAPI *lock)(void);
|
void (WINAPI *lock)(void);
|
||||||
void (WINAPI *unlock)(void);
|
void (WINAPI *unlock)(void);
|
||||||
int (WINAPI *cond_wait)(void);
|
int (WINAPI *cond_wait)(void);
|
||||||
void (WINAPI *broadcast)(void);
|
void (WINAPI *broadcast)(void);
|
||||||
|
HRESULT (WINAPI *test_connect)(const char *name, struct pulse_config *config);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue