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
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue