2021-05-11 18:29:38 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2011-2012 Maarten Lankhorst
|
|
|
|
* Copyright 2010-2011 Maarten Lankhorst for CodeWeavers
|
|
|
|
* Copyright 2011 Andrew Eikum for CodeWeavers
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
#pragma makedep unix
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <pthread.h>
|
2021-05-11 18:31:45 +02:00
|
|
|
#include <math.h>
|
2021-05-11 18:30:00 +02:00
|
|
|
#include <poll.h>
|
|
|
|
|
|
|
|
#include <pulse/pulseaudio.h>
|
2021-05-11 18:29:38 +02:00
|
|
|
|
|
|
|
#include "ntstatus.h"
|
|
|
|
#define WIN32_NO_STATUS
|
|
|
|
#include "winternl.h"
|
|
|
|
|
2021-05-11 18:31:24 +02:00
|
|
|
#include "mmdeviceapi.h"
|
2021-05-11 18:30:00 +02:00
|
|
|
#include "initguid.h"
|
|
|
|
#include "audioclient.h"
|
|
|
|
|
2021-05-11 18:29:38 +02:00
|
|
|
#include "unixlib.h"
|
|
|
|
|
2021-05-11 18:30:00 +02:00
|
|
|
#include "wine/debug.h"
|
|
|
|
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(pulse);
|
|
|
|
|
2021-05-24 18:21:56 +02:00
|
|
|
struct pulse_stream
|
|
|
|
{
|
|
|
|
EDataFlow dataflow;
|
|
|
|
|
|
|
|
pa_stream *stream;
|
|
|
|
pa_sample_spec ss;
|
|
|
|
pa_channel_map map;
|
|
|
|
pa_buffer_attr attr;
|
|
|
|
|
|
|
|
DWORD flags;
|
|
|
|
AUDCLNT_SHAREMODE share;
|
|
|
|
HANDLE event;
|
|
|
|
float vol[PA_CHANNELS_MAX];
|
|
|
|
|
|
|
|
INT32 locked;
|
2021-05-26 16:19:00 +02:00
|
|
|
BOOL started;
|
2022-01-26 14:26:12 +01:00
|
|
|
SIZE_T bufsize_frames, real_bufsize_bytes, period_bytes;
|
2021-05-26 16:19:00 +02:00
|
|
|
SIZE_T peek_ofs, read_offs_bytes, lcl_offs_bytes, pa_offs_bytes;
|
2021-05-26 16:18:53 +02:00
|
|
|
SIZE_T tmp_buffer_bytes, held_bytes, peek_len, peek_buffer_len, pa_held_bytes;
|
2021-05-24 18:21:56 +02:00
|
|
|
BYTE *local_buffer, *tmp_buffer, *peek_buffer;
|
|
|
|
void *locked_ptr;
|
|
|
|
BOOL please_quit, just_started, just_underran;
|
|
|
|
pa_usec_t last_time, mmdev_period_usec;
|
|
|
|
|
|
|
|
INT64 clock_lastpos, clock_written;
|
|
|
|
|
|
|
|
struct list packet_free_head;
|
|
|
|
struct list packet_filled_head;
|
|
|
|
};
|
|
|
|
|
2021-05-19 22:27:30 +02:00
|
|
|
typedef struct _ACPacket
|
|
|
|
{
|
|
|
|
struct list entry;
|
|
|
|
UINT64 qpcpos;
|
|
|
|
BYTE *data;
|
|
|
|
UINT32 discont;
|
|
|
|
} ACPacket;
|
|
|
|
|
2021-05-11 18:30:00 +02:00
|
|
|
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;
|
|
|
|
|
2021-05-11 18:29:38 +02:00
|
|
|
static pthread_mutex_t pulse_mutex;
|
|
|
|
static pthread_cond_t pulse_cond = PTHREAD_COND_INITIALIZER;
|
|
|
|
|
2021-05-13 16:04:56 +02:00
|
|
|
UINT8 mult_alaw_sample(UINT8, float);
|
|
|
|
UINT8 mult_ulaw_sample(UINT8, float);
|
|
|
|
|
2021-05-26 16:18:43 +02:00
|
|
|
static void pulse_lock(void)
|
2021-05-11 18:29:38 +02:00
|
|
|
{
|
|
|
|
pthread_mutex_lock(&pulse_mutex);
|
|
|
|
}
|
|
|
|
|
2021-05-26 16:18:43 +02:00
|
|
|
static void pulse_unlock(void)
|
2021-05-11 18:29:38 +02:00
|
|
|
{
|
|
|
|
pthread_mutex_unlock(&pulse_mutex);
|
|
|
|
}
|
|
|
|
|
2021-05-26 16:18:36 +02:00
|
|
|
static int pulse_cond_wait(void)
|
2021-05-11 18:29:38 +02:00
|
|
|
{
|
|
|
|
return pthread_cond_wait(&pulse_cond, &pulse_mutex);
|
|
|
|
}
|
|
|
|
|
2021-05-18 16:58:48 +02:00
|
|
|
static void pulse_broadcast(void)
|
2021-05-11 18:29:38 +02:00
|
|
|
{
|
|
|
|
pthread_cond_broadcast(&pulse_cond);
|
|
|
|
}
|
|
|
|
|
2021-05-11 18:31:45 +02:00
|
|
|
static void dump_attr(const pa_buffer_attr *attr)
|
|
|
|
{
|
|
|
|
TRACE("maxlength: %u\n", attr->maxlength);
|
|
|
|
TRACE("minreq: %u\n", attr->minreq);
|
|
|
|
TRACE("fragsize: %u\n", attr->fragsize);
|
|
|
|
TRACE("tlength: %u\n", attr->tlength);
|
|
|
|
TRACE("prebuf: %u\n", attr->prebuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* copied from kernelbase */
|
|
|
|
static int muldiv(int a, int b, int c)
|
|
|
|
{
|
|
|
|
LONGLONG ret;
|
|
|
|
|
|
|
|
if (!c) return -1;
|
|
|
|
|
|
|
|
/* We want to deal with a positive divisor to simplify the logic. */
|
|
|
|
if (c < 0)
|
|
|
|
{
|
|
|
|
a = -a;
|
|
|
|
c = -c;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the result is positive, we "add" to round. else, we subtract to round. */
|
|
|
|
if ((a < 0 && b < 0) || (a >= 0 && b >= 0))
|
|
|
|
ret = (((LONGLONG)a * b) + (c / 2)) / c;
|
|
|
|
else
|
|
|
|
ret = (((LONGLONG)a * b) - (c / 2)) / c;
|
|
|
|
|
|
|
|
if (ret > 2147483647 || ret < -2147483647) return -1;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-05-11 18:30:17 +02:00
|
|
|
/* Following pulseaudio design here, mainloop has the lock taken whenever
|
|
|
|
* it is handling something for pulse, and the lock is required whenever
|
|
|
|
* doing any pa_* call that can affect the state in any way
|
|
|
|
*
|
|
|
|
* pa_cond_wait is used when waiting on results, because the mainloop needs
|
|
|
|
* the same lock taken to affect the state
|
|
|
|
*
|
|
|
|
* This is basically the same as the pa_threaded_mainloop implementation,
|
|
|
|
* but that cannot be used because it uses pthread_create directly
|
|
|
|
*
|
|
|
|
* pa_threaded_mainloop_(un)lock -> pthread_mutex_(un)lock
|
|
|
|
* pa_threaded_mainloop_signal -> pthread_cond_broadcast
|
|
|
|
* pa_threaded_mainloop_wait -> pthread_cond_wait
|
|
|
|
*/
|
2021-05-11 18:30:00 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-08-10 18:02:39 +02:00
|
|
|
static NTSTATUS pulse_process_attach(void *args)
|
|
|
|
{
|
|
|
|
pthread_mutexattr_t attr;
|
|
|
|
|
|
|
|
pthread_mutexattr_init(&attr);
|
|
|
|
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
|
|
|
|
|
|
|
|
if (pthread_mutex_init(&pulse_mutex, &attr) != 0)
|
|
|
|
pthread_mutex_init(&pulse_mutex, NULL);
|
|
|
|
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static NTSTATUS pulse_process_detach(void *args)
|
|
|
|
{
|
|
|
|
if (pulse_ctx)
|
|
|
|
{
|
|
|
|
pa_context_disconnect(pulse_ctx);
|
|
|
|
pa_context_unref(pulse_ctx);
|
|
|
|
}
|
|
|
|
if (pulse_ml)
|
|
|
|
pa_mainloop_quit(pulse_ml, 0);
|
|
|
|
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_main_loop(void *args)
|
2021-05-11 18:30:17 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct main_loop_params *params = args;
|
2021-05-11 18:30:17 +02:00
|
|
|
int ret;
|
2021-05-26 16:18:43 +02:00
|
|
|
pulse_lock();
|
2021-05-11 18:30:17 +02:00
|
|
|
pulse_ml = pa_mainloop_new();
|
|
|
|
pa_mainloop_set_poll_func(pulse_ml, pulse_poll_func, NULL);
|
2021-05-28 19:05:01 +02:00
|
|
|
NtSetEvent(params->event, NULL);
|
2021-05-11 18:30:17 +02:00
|
|
|
pa_mainloop_run(pulse_ml, &ret);
|
|
|
|
pa_mainloop_free(pulse_ml);
|
2021-05-26 16:18:43 +02:00
|
|
|
pulse_unlock();
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-11 18:30:17 +02:00
|
|
|
}
|
|
|
|
|
2021-05-11 18:30:00 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2021-05-11 18:31:45 +02:00
|
|
|
static void pulse_attr_update(pa_stream *s, void *user) {
|
|
|
|
const pa_buffer_attr *attr = pa_stream_get_buffer_attr(s);
|
|
|
|
TRACE("New attributes or device moved:\n");
|
|
|
|
dump_attr(attr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pulse_underflow_callback(pa_stream *s, void *userdata)
|
2021-05-11 18:30:17 +02:00
|
|
|
{
|
2021-05-11 18:31:45 +02:00
|
|
|
struct pulse_stream *stream = userdata;
|
|
|
|
WARN("%p: Underflow\n", userdata);
|
|
|
|
stream->just_underran = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pulse_started_callback(pa_stream *s, void *userdata)
|
|
|
|
{
|
|
|
|
TRACE("%p: (Re)started playing\n", userdata);
|
|
|
|
}
|
|
|
|
|
2021-05-13 16:03:29 +02:00
|
|
|
static void pulse_op_cb(pa_stream *s, int success, void *user)
|
|
|
|
{
|
|
|
|
TRACE("Success: %i\n", success);
|
|
|
|
*(int*)user = success;
|
|
|
|
pulse_broadcast();
|
|
|
|
}
|
|
|
|
|
2021-05-11 18:31:45 +02:00
|
|
|
static void silence_buffer(pa_sample_format_t format, BYTE *buffer, UINT32 bytes)
|
|
|
|
{
|
|
|
|
memset(buffer, format == PA_SAMPLE_U8 ? 0x80 : 0, bytes);
|
|
|
|
}
|
|
|
|
|
2021-05-13 16:03:29 +02:00
|
|
|
static BOOL pulse_stream_valid(struct pulse_stream *stream)
|
|
|
|
{
|
|
|
|
return pa_stream_get_state(stream->stream) == PA_STREAM_READY;
|
|
|
|
}
|
|
|
|
|
2021-05-11 18:31:45 +02:00
|
|
|
static HRESULT pulse_connect(const char *name)
|
|
|
|
{
|
|
|
|
if (pulse_ctx && PA_CONTEXT_IS_GOOD(pa_context_get_state(pulse_ctx)))
|
2021-05-11 18:30:17 +02:00
|
|
|
return S_OK;
|
|
|
|
if (pulse_ctx)
|
|
|
|
pa_context_unref(pulse_ctx);
|
|
|
|
|
|
|
|
pulse_ctx = pa_context_new(pa_mainloop_get_api(pulse_ml), name);
|
|
|
|
if (!pulse_ctx) {
|
|
|
|
ERR("Failed to create context\n");
|
|
|
|
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 (pulse_cond_wait()) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
TRACE("Connected to server %s with protocol version: %i.\n",
|
|
|
|
pa_context_get_server(pulse_ctx),
|
|
|
|
pa_context_get_server_protocol_version(pulse_ctx));
|
|
|
|
return S_OK;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
pa_context_unref(pulse_ctx);
|
|
|
|
pulse_ctx = NULL;
|
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
|
2021-05-11 18:30:00 +02:00
|
|
|
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. */
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_test_connect(void *args)
|
2021-05-11 18:30:00 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct test_connect_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_config *config = params->config;
|
2021-05-11 18:30:00 +02:00
|
|
|
pa_operation *o;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
pulse_lock();
|
|
|
|
pulse_ml = pa_mainloop_new();
|
|
|
|
|
|
|
|
pa_mainloop_set_poll_func(pulse_ml, pulse_poll_func, NULL);
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
pulse_ctx = pa_context_new(pa_mainloop_get_api(pulse_ml), params->name);
|
2021-05-11 18:30:00 +02:00
|
|
|
if (!pulse_ctx) {
|
|
|
|
ERR("Failed to create context\n");
|
|
|
|
pa_mainloop_free(pulse_ml);
|
|
|
|
pulse_ml = NULL;
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = E_FAIL;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-11 18:30:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-11 18:30:00 +02:00
|
|
|
|
|
|
|
fail:
|
|
|
|
pa_context_unref(pulse_ctx);
|
|
|
|
pulse_ctx = NULL;
|
|
|
|
pa_mainloop_free(pulse_ml);
|
|
|
|
pulse_ml = NULL;
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = E_FAIL;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-11 18:30:00 +02:00
|
|
|
}
|
|
|
|
|
2021-05-11 18:31:45 +02:00
|
|
|
static DWORD get_channel_mask(unsigned int channels)
|
|
|
|
{
|
|
|
|
switch(channels) {
|
|
|
|
case 0:
|
|
|
|
return 0;
|
|
|
|
case 1:
|
|
|
|
return KSAUDIO_SPEAKER_MONO;
|
|
|
|
case 2:
|
|
|
|
return KSAUDIO_SPEAKER_STEREO;
|
|
|
|
case 3:
|
|
|
|
return KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY;
|
|
|
|
case 4:
|
|
|
|
return KSAUDIO_SPEAKER_QUAD; /* not _SURROUND */
|
|
|
|
case 5:
|
|
|
|
return KSAUDIO_SPEAKER_QUAD | SPEAKER_LOW_FREQUENCY;
|
|
|
|
case 6:
|
|
|
|
return KSAUDIO_SPEAKER_5POINT1; /* not 5POINT1_SURROUND */
|
|
|
|
case 7:
|
|
|
|
return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER;
|
|
|
|
case 8:
|
|
|
|
return KSAUDIO_SPEAKER_7POINT1_SURROUND; /* Vista deprecates 7POINT1 */
|
|
|
|
}
|
|
|
|
FIXME("Unknown speaker configuration: %u\n", channels);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const enum pa_channel_position pulse_pos_from_wfx[] = {
|
|
|
|
PA_CHANNEL_POSITION_FRONT_LEFT,
|
|
|
|
PA_CHANNEL_POSITION_FRONT_RIGHT,
|
|
|
|
PA_CHANNEL_POSITION_FRONT_CENTER,
|
|
|
|
PA_CHANNEL_POSITION_LFE,
|
|
|
|
PA_CHANNEL_POSITION_REAR_LEFT,
|
|
|
|
PA_CHANNEL_POSITION_REAR_RIGHT,
|
|
|
|
PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
|
|
|
|
PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
|
|
|
|
PA_CHANNEL_POSITION_REAR_CENTER,
|
|
|
|
PA_CHANNEL_POSITION_SIDE_LEFT,
|
|
|
|
PA_CHANNEL_POSITION_SIDE_RIGHT,
|
|
|
|
PA_CHANNEL_POSITION_TOP_CENTER,
|
|
|
|
PA_CHANNEL_POSITION_TOP_FRONT_LEFT,
|
|
|
|
PA_CHANNEL_POSITION_TOP_FRONT_CENTER,
|
|
|
|
PA_CHANNEL_POSITION_TOP_FRONT_RIGHT,
|
|
|
|
PA_CHANNEL_POSITION_TOP_REAR_LEFT,
|
|
|
|
PA_CHANNEL_POSITION_TOP_REAR_CENTER,
|
|
|
|
PA_CHANNEL_POSITION_TOP_REAR_RIGHT
|
|
|
|
};
|
|
|
|
|
|
|
|
static HRESULT pulse_spec_from_waveformat(struct pulse_stream *stream, const WAVEFORMATEX *fmt)
|
|
|
|
{
|
|
|
|
pa_channel_map_init(&stream->map);
|
|
|
|
stream->ss.rate = fmt->nSamplesPerSec;
|
|
|
|
stream->ss.format = PA_SAMPLE_INVALID;
|
|
|
|
|
|
|
|
switch(fmt->wFormatTag) {
|
|
|
|
case WAVE_FORMAT_IEEE_FLOAT:
|
|
|
|
if (!fmt->nChannels || fmt->nChannels > 2 || fmt->wBitsPerSample != 32)
|
|
|
|
break;
|
|
|
|
stream->ss.format = PA_SAMPLE_FLOAT32LE;
|
|
|
|
pa_channel_map_init_auto(&stream->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA);
|
|
|
|
break;
|
|
|
|
case WAVE_FORMAT_PCM:
|
|
|
|
if (!fmt->nChannels || fmt->nChannels > 2)
|
|
|
|
break;
|
|
|
|
if (fmt->wBitsPerSample == 8)
|
|
|
|
stream->ss.format = PA_SAMPLE_U8;
|
|
|
|
else if (fmt->wBitsPerSample == 16)
|
|
|
|
stream->ss.format = PA_SAMPLE_S16LE;
|
|
|
|
else
|
|
|
|
return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
|
|
|
pa_channel_map_init_auto(&stream->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA);
|
|
|
|
break;
|
|
|
|
case WAVE_FORMAT_EXTENSIBLE: {
|
|
|
|
WAVEFORMATEXTENSIBLE *wfe = (WAVEFORMATEXTENSIBLE*)fmt;
|
|
|
|
DWORD mask = wfe->dwChannelMask;
|
|
|
|
DWORD i = 0, j;
|
|
|
|
if (fmt->cbSize != (sizeof(*wfe) - sizeof(*fmt)) && fmt->cbSize != sizeof(*wfe))
|
|
|
|
break;
|
|
|
|
if (IsEqualGUID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) &&
|
|
|
|
(!wfe->Samples.wValidBitsPerSample || wfe->Samples.wValidBitsPerSample == 32) &&
|
|
|
|
fmt->wBitsPerSample == 32)
|
|
|
|
stream->ss.format = PA_SAMPLE_FLOAT32LE;
|
|
|
|
else if (IsEqualGUID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) {
|
|
|
|
DWORD valid = wfe->Samples.wValidBitsPerSample;
|
|
|
|
if (!valid)
|
|
|
|
valid = fmt->wBitsPerSample;
|
|
|
|
if (!valid || valid > fmt->wBitsPerSample)
|
|
|
|
break;
|
|
|
|
switch (fmt->wBitsPerSample) {
|
|
|
|
case 8:
|
|
|
|
if (valid == 8)
|
|
|
|
stream->ss.format = PA_SAMPLE_U8;
|
|
|
|
break;
|
|
|
|
case 16:
|
|
|
|
if (valid == 16)
|
|
|
|
stream->ss.format = PA_SAMPLE_S16LE;
|
|
|
|
break;
|
|
|
|
case 24:
|
|
|
|
if (valid == 24)
|
|
|
|
stream->ss.format = PA_SAMPLE_S24LE;
|
|
|
|
break;
|
|
|
|
case 32:
|
|
|
|
if (valid == 24)
|
|
|
|
stream->ss.format = PA_SAMPLE_S24_32LE;
|
|
|
|
else if (valid == 32)
|
|
|
|
stream->ss.format = PA_SAMPLE_S32LE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stream->map.channels = fmt->nChannels;
|
|
|
|
if (!mask || (mask & (SPEAKER_ALL|SPEAKER_RESERVED)))
|
|
|
|
mask = get_channel_mask(fmt->nChannels);
|
|
|
|
for (j = 0; j < ARRAY_SIZE(pulse_pos_from_wfx) && i < fmt->nChannels; ++j) {
|
|
|
|
if (mask & (1 << j))
|
|
|
|
stream->map.map[i++] = pulse_pos_from_wfx[j];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Special case for mono since pulse appears to map it differently */
|
|
|
|
if (mask == SPEAKER_FRONT_CENTER)
|
|
|
|
stream->map.map[0] = PA_CHANNEL_POSITION_MONO;
|
|
|
|
|
|
|
|
if (i < fmt->nChannels || (mask & SPEAKER_RESERVED)) {
|
|
|
|
stream->map.channels = 0;
|
|
|
|
ERR("Invalid channel mask: %i/%i and %x(%x)\n", i, fmt->nChannels, mask, wfe->dwChannelMask);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case WAVE_FORMAT_ALAW:
|
|
|
|
case WAVE_FORMAT_MULAW:
|
|
|
|
if (fmt->wBitsPerSample != 8) {
|
|
|
|
FIXME("Unsupported bpp %u for LAW\n", fmt->wBitsPerSample);
|
|
|
|
return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
|
|
|
}
|
|
|
|
if (fmt->nChannels != 1 && fmt->nChannels != 2) {
|
|
|
|
FIXME("Unsupported channels %u for LAW\n", fmt->nChannels);
|
|
|
|
return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
|
|
|
}
|
|
|
|
stream->ss.format = fmt->wFormatTag == WAVE_FORMAT_MULAW ? PA_SAMPLE_ULAW : PA_SAMPLE_ALAW;
|
|
|
|
pa_channel_map_init_auto(&stream->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
WARN("Unhandled tag %x\n", fmt->wFormatTag);
|
|
|
|
return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
|
|
|
}
|
|
|
|
stream->ss.channels = stream->map.channels;
|
|
|
|
if (!pa_channel_map_valid(&stream->map) || stream->ss.format == PA_SAMPLE_INVALID) {
|
|
|
|
ERR("Invalid format! Channel spec valid: %i, format: %i\n",
|
|
|
|
pa_channel_map_valid(&stream->map), stream->ss.format);
|
|
|
|
return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
|
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static HRESULT pulse_stream_connect(struct pulse_stream *stream, UINT32 period_bytes)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
char buffer[64];
|
|
|
|
static LONG number;
|
|
|
|
pa_buffer_attr attr;
|
|
|
|
|
|
|
|
ret = InterlockedIncrement(&number);
|
|
|
|
sprintf(buffer, "audio stream #%i", ret);
|
|
|
|
stream->stream = pa_stream_new(pulse_ctx, buffer, &stream->ss, &stream->map);
|
|
|
|
|
|
|
|
if (!stream->stream) {
|
|
|
|
WARN("pa_stream_new returned error %i\n", pa_context_errno(pulse_ctx));
|
|
|
|
return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
|
|
|
|
}
|
|
|
|
|
|
|
|
pa_stream_set_state_callback(stream->stream, pulse_stream_state, stream);
|
|
|
|
pa_stream_set_buffer_attr_callback(stream->stream, pulse_attr_update, stream);
|
|
|
|
pa_stream_set_moved_callback(stream->stream, pulse_attr_update, stream);
|
|
|
|
|
|
|
|
/* PulseAudio will fill in correct values */
|
|
|
|
attr.minreq = attr.fragsize = period_bytes;
|
|
|
|
attr.tlength = period_bytes * 3;
|
|
|
|
attr.maxlength = stream->bufsize_frames * pa_frame_size(&stream->ss);
|
|
|
|
attr.prebuf = pa_frame_size(&stream->ss);
|
|
|
|
dump_attr(&attr);
|
|
|
|
if (stream->dataflow == eRender)
|
|
|
|
ret = pa_stream_connect_playback(stream->stream, NULL, &attr,
|
|
|
|
PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY, NULL, NULL);
|
|
|
|
else
|
|
|
|
ret = pa_stream_connect_record(stream->stream, NULL, &attr,
|
|
|
|
PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY);
|
|
|
|
if (ret < 0) {
|
|
|
|
WARN("Returns %i\n", ret);
|
|
|
|
return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
|
|
|
|
}
|
|
|
|
while (pa_stream_get_state(stream->stream) == PA_STREAM_CREATING)
|
|
|
|
pulse_cond_wait();
|
|
|
|
if (pa_stream_get_state(stream->stream) != PA_STREAM_READY)
|
|
|
|
return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
|
|
|
|
|
|
|
|
if (stream->dataflow == eRender) {
|
|
|
|
pa_stream_set_underflow_callback(stream->stream, pulse_underflow_callback, stream);
|
|
|
|
pa_stream_set_started_callback(stream->stream, pulse_started_callback, stream);
|
|
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_create_stream(void *args)
|
2021-05-11 18:31:45 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct create_stream_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
REFERENCE_TIME period, duration = params->duration;
|
2021-05-11 18:31:45 +02:00
|
|
|
struct pulse_stream *stream;
|
2021-05-13 16:03:38 +02:00
|
|
|
unsigned int i, bufsize_bytes;
|
2021-05-11 18:31:45 +02:00
|
|
|
HRESULT hr;
|
|
|
|
|
2021-05-26 16:18:43 +02:00
|
|
|
pulse_lock();
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
if (FAILED(params->result = pulse_connect(params->name)))
|
2021-05-26 16:18:43 +02:00
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-26 16:18:43 +02:00
|
|
|
}
|
2021-05-11 18:31:45 +02:00
|
|
|
|
2021-05-26 16:18:47 +02:00
|
|
|
if (!(stream = calloc(1, sizeof(*stream))))
|
2021-05-26 16:18:43 +02:00
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = E_OUTOFMEMORY;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-26 16:18:43 +02:00
|
|
|
}
|
2021-05-11 18:31:45 +02:00
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
stream->dataflow = params->dataflow;
|
2021-05-13 16:03:38 +02:00
|
|
|
for (i = 0; i < ARRAY_SIZE(stream->vol); ++i)
|
|
|
|
stream->vol[i] = 1.f;
|
2021-05-11 18:31:45 +02:00
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
hr = pulse_spec_from_waveformat(stream, params->fmt);
|
2021-05-11 18:31:45 +02:00
|
|
|
TRACE("Obtaining format returns %08x\n", hr);
|
|
|
|
|
|
|
|
if (FAILED(hr))
|
|
|
|
goto exit;
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
period = pulse_def_period[stream->dataflow == eCapture];
|
2021-05-11 18:31:45 +02:00
|
|
|
if (duration < 3 * period)
|
|
|
|
duration = 3 * period;
|
|
|
|
|
|
|
|
stream->period_bytes = pa_frame_size(&stream->ss) * muldiv(period, stream->ss.rate, 10000000);
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
stream->bufsize_frames = ceil((duration / 10000000.) * params->fmt->nSamplesPerSec);
|
2021-05-11 18:31:45 +02:00
|
|
|
bufsize_bytes = stream->bufsize_frames * pa_frame_size(&stream->ss);
|
|
|
|
stream->mmdev_period_usec = period / 10;
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
stream->share = params->mode;
|
|
|
|
stream->flags = params->flags;
|
2021-05-11 18:31:45 +02:00
|
|
|
hr = pulse_stream_connect(stream, stream->period_bytes);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
UINT32 unalign;
|
|
|
|
const pa_buffer_attr *attr = pa_stream_get_buffer_attr(stream->stream);
|
2022-01-26 14:26:12 +01:00
|
|
|
SIZE_T size;
|
|
|
|
|
2021-05-11 18:31:45 +02:00
|
|
|
stream->attr = *attr;
|
|
|
|
/* Update frames according to new size */
|
|
|
|
dump_attr(attr);
|
2021-05-28 19:05:01 +02:00
|
|
|
if (stream->dataflow == eRender) {
|
2022-01-26 14:26:12 +01:00
|
|
|
size = stream->real_bufsize_bytes =
|
2021-05-26 16:19:00 +02:00
|
|
|
stream->bufsize_frames * 2 * pa_frame_size(&stream->ss);
|
|
|
|
if (NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer,
|
2022-01-26 14:26:12 +01:00
|
|
|
0, &size, MEM_COMMIT, PAGE_READWRITE))
|
2021-05-11 18:31:45 +02:00
|
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
} else {
|
|
|
|
UINT32 i, capture_packets;
|
|
|
|
|
|
|
|
if ((unalign = bufsize_bytes % stream->period_bytes))
|
|
|
|
bufsize_bytes += stream->period_bytes - unalign;
|
|
|
|
stream->bufsize_frames = bufsize_bytes / pa_frame_size(&stream->ss);
|
|
|
|
stream->real_bufsize_bytes = bufsize_bytes;
|
|
|
|
|
|
|
|
capture_packets = stream->real_bufsize_bytes / stream->period_bytes;
|
|
|
|
|
2022-01-26 14:26:12 +01:00
|
|
|
size = stream->real_bufsize_bytes + capture_packets * sizeof(ACPacket);
|
2021-05-26 16:19:00 +02:00
|
|
|
if (NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer,
|
2022-01-26 14:26:12 +01:00
|
|
|
0, &size, MEM_COMMIT, PAGE_READWRITE))
|
2021-05-11 18:31:45 +02:00
|
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
else {
|
|
|
|
ACPacket *cur_packet = (ACPacket*)((char*)stream->local_buffer + stream->real_bufsize_bytes);
|
|
|
|
BYTE *data = stream->local_buffer;
|
|
|
|
silence_buffer(stream->ss.format, stream->local_buffer, stream->real_bufsize_bytes);
|
|
|
|
list_init(&stream->packet_free_head);
|
|
|
|
list_init(&stream->packet_filled_head);
|
|
|
|
for (i = 0; i < capture_packets; ++i, ++cur_packet) {
|
|
|
|
list_add_tail(&stream->packet_free_head, &cur_packet->entry);
|
|
|
|
cur_packet->data = data;
|
|
|
|
data += stream->period_bytes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->channel_count = stream->ss.channels;
|
|
|
|
*params->stream = stream;
|
2021-05-26 16:18:43 +02:00
|
|
|
|
2021-05-11 18:31:45 +02:00
|
|
|
exit:
|
2021-05-28 19:05:01 +02:00
|
|
|
if (FAILED(params->result = hr)) {
|
2021-05-11 18:31:45 +02:00
|
|
|
free(stream->local_buffer);
|
|
|
|
if (stream->stream) {
|
|
|
|
pa_stream_disconnect(stream->stream);
|
|
|
|
pa_stream_unref(stream->stream);
|
2021-05-26 16:18:47 +02:00
|
|
|
free(stream);
|
2021-05-11 18:31:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-26 16:18:43 +02:00
|
|
|
pulse_unlock();
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-11 18:31:45 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_release_stream(void *args)
|
2021-05-11 18:31:34 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct release_stream_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
2022-01-14 17:04:17 +01:00
|
|
|
SIZE_T size;
|
2021-05-28 19:05:01 +02:00
|
|
|
|
|
|
|
if(params->timer) {
|
2021-05-11 18:31:34 +02:00
|
|
|
stream->please_quit = TRUE;
|
2021-05-28 19:05:01 +02:00
|
|
|
NtWaitForSingleObject(params->timer, FALSE, NULL);
|
|
|
|
NtClose(params->timer);
|
2021-05-11 18:31:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pulse_lock();
|
|
|
|
if (PA_STREAM_IS_GOOD(pa_stream_get_state(stream->stream))) {
|
|
|
|
pa_stream_disconnect(stream->stream);
|
|
|
|
while (PA_STREAM_IS_GOOD(pa_stream_get_state(stream->stream)))
|
|
|
|
pulse_cond_wait();
|
|
|
|
}
|
|
|
|
pa_stream_unref(stream->stream);
|
|
|
|
pulse_unlock();
|
|
|
|
|
2022-01-14 17:04:17 +01:00
|
|
|
if (stream->tmp_buffer) {
|
|
|
|
size = 0;
|
2021-05-26 16:18:53 +02:00
|
|
|
NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer,
|
2022-01-14 17:04:17 +01:00
|
|
|
&size, MEM_RELEASE);
|
|
|
|
}
|
|
|
|
if (stream->local_buffer) {
|
|
|
|
size = 0;
|
2021-05-26 16:19:00 +02:00
|
|
|
NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer,
|
2022-01-14 17:04:17 +01:00
|
|
|
&size, MEM_RELEASE);
|
|
|
|
}
|
2021-05-26 16:18:47 +02:00
|
|
|
free(stream->peek_buffer);
|
|
|
|
free(stream);
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-11 18:31:34 +02:00
|
|
|
}
|
|
|
|
|
2021-05-13 16:04:56 +02:00
|
|
|
static int write_buffer(const struct pulse_stream *stream, BYTE *buffer, UINT32 bytes)
|
|
|
|
{
|
|
|
|
const float *vol = stream->vol;
|
|
|
|
UINT32 i, channels, mute = 0;
|
|
|
|
BOOL adjust = FALSE;
|
|
|
|
BYTE *end;
|
|
|
|
|
|
|
|
if (!bytes) return 0;
|
|
|
|
|
|
|
|
/* Adjust the buffer based on the volume for each channel */
|
|
|
|
channels = stream->ss.channels;
|
|
|
|
for (i = 0; i < channels; i++)
|
|
|
|
{
|
|
|
|
adjust |= vol[i] != 1.0f;
|
|
|
|
if (vol[i] == 0.0f)
|
|
|
|
mute++;
|
|
|
|
}
|
|
|
|
if (mute == channels)
|
|
|
|
{
|
|
|
|
silence_buffer(stream->ss.format, buffer, bytes);
|
|
|
|
goto write;
|
|
|
|
}
|
|
|
|
if (!adjust) goto write;
|
|
|
|
|
|
|
|
end = buffer + bytes;
|
|
|
|
switch (stream->ss.format)
|
|
|
|
{
|
|
|
|
#ifndef WORDS_BIGENDIAN
|
|
|
|
#define PROCESS_BUFFER(type) do \
|
|
|
|
{ \
|
|
|
|
type *p = (type*)buffer; \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
for (i = 0; i < channels; i++) \
|
|
|
|
p[i] = p[i] * vol[i]; \
|
|
|
|
p += i; \
|
|
|
|
} while ((BYTE*)p != end); \
|
|
|
|
} while (0)
|
|
|
|
case PA_SAMPLE_S16LE:
|
|
|
|
PROCESS_BUFFER(INT16);
|
|
|
|
break;
|
|
|
|
case PA_SAMPLE_S32LE:
|
|
|
|
PROCESS_BUFFER(INT32);
|
|
|
|
break;
|
|
|
|
case PA_SAMPLE_FLOAT32LE:
|
|
|
|
PROCESS_BUFFER(float);
|
|
|
|
break;
|
|
|
|
#undef PROCESS_BUFFER
|
|
|
|
case PA_SAMPLE_S24_32LE:
|
|
|
|
{
|
|
|
|
UINT32 *p = (UINT32*)buffer;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
for (i = 0; i < channels; i++)
|
|
|
|
{
|
|
|
|
p[i] = (INT32)((INT32)(p[i] << 8) * vol[i]);
|
|
|
|
p[i] >>= 8;
|
|
|
|
}
|
|
|
|
p += i;
|
|
|
|
} while ((BYTE*)p != end);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case PA_SAMPLE_S24LE:
|
|
|
|
{
|
|
|
|
/* do it 12 bytes at a time until it is no longer possible */
|
|
|
|
UINT32 *q = (UINT32*)buffer;
|
|
|
|
BYTE *p;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while (end - (BYTE*)q >= 12)
|
|
|
|
{
|
|
|
|
UINT32 v[4], k;
|
|
|
|
v[0] = q[0] << 8;
|
|
|
|
v[1] = q[1] << 16 | (q[0] >> 16 & ~0xff);
|
|
|
|
v[2] = q[2] << 24 | (q[1] >> 8 & ~0xff);
|
|
|
|
v[3] = q[2] & ~0xff;
|
|
|
|
for (k = 0; k < 4; k++)
|
|
|
|
{
|
|
|
|
v[k] = (INT32)((INT32)v[k] * vol[i]);
|
|
|
|
if (++i == channels) i = 0;
|
|
|
|
}
|
|
|
|
*q++ = v[0] >> 8 | (v[1] & ~0xff) << 16;
|
|
|
|
*q++ = v[1] >> 16 | (v[2] & ~0xff) << 8;
|
|
|
|
*q++ = v[2] >> 24 | (v[3] & ~0xff);
|
|
|
|
}
|
|
|
|
p = (BYTE*)q;
|
|
|
|
while (p != end)
|
|
|
|
{
|
|
|
|
UINT32 v = (INT32)((INT32)(p[0] << 8 | p[1] << 16 | p[2] << 24) * vol[i]);
|
|
|
|
*p++ = v >> 8 & 0xff;
|
|
|
|
*p++ = v >> 16 & 0xff;
|
|
|
|
*p++ = v >> 24;
|
|
|
|
if (++i == channels) i = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
case PA_SAMPLE_U8:
|
|
|
|
{
|
|
|
|
UINT8 *p = (UINT8*)buffer;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
for (i = 0; i < channels; i++)
|
|
|
|
p[i] = (int)((p[i] - 128) * vol[i]) + 128;
|
|
|
|
p += i;
|
|
|
|
} while ((BYTE*)p != end);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case PA_SAMPLE_ALAW:
|
|
|
|
{
|
|
|
|
UINT8 *p = (UINT8*)buffer;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
for (i = 0; i < channels; i++)
|
|
|
|
p[i] = mult_alaw_sample(p[i], vol[i]);
|
|
|
|
p += i;
|
|
|
|
} while ((BYTE*)p != end);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case PA_SAMPLE_ULAW:
|
|
|
|
{
|
|
|
|
UINT8 *p = (UINT8*)buffer;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
for (i = 0; i < channels; i++)
|
|
|
|
p[i] = mult_ulaw_sample(p[i], vol[i]);
|
|
|
|
p += i;
|
|
|
|
} while ((BYTE*)p != end);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
TRACE("Unhandled format %i, not adjusting volume.\n", stream->ss.format);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
write:
|
|
|
|
return pa_stream_write(stream->stream, buffer, bytes, NULL, 0, PA_SEEK_RELATIVE);
|
|
|
|
}
|
|
|
|
|
2021-05-18 16:57:18 +02:00
|
|
|
static void pulse_write(struct pulse_stream *stream)
|
2021-05-13 16:04:56 +02:00
|
|
|
{
|
|
|
|
/* write as much data to PA as we can */
|
|
|
|
UINT32 to_write;
|
|
|
|
BYTE *buf = stream->local_buffer + stream->pa_offs_bytes;
|
|
|
|
UINT32 bytes = pa_stream_writable_size(stream->stream);
|
|
|
|
|
|
|
|
if (stream->just_underran)
|
|
|
|
{
|
|
|
|
/* prebuffer with silence if needed */
|
|
|
|
if(stream->pa_held_bytes < bytes){
|
|
|
|
to_write = bytes - stream->pa_held_bytes;
|
|
|
|
TRACE("prebuffering %u frames of silence\n",
|
|
|
|
(int)(to_write / pa_frame_size(&stream->ss)));
|
2021-05-26 16:18:47 +02:00
|
|
|
buf = calloc(1, to_write);
|
2021-05-13 16:04:56 +02:00
|
|
|
pa_stream_write(stream->stream, buf, to_write, NULL, 0, PA_SEEK_RELATIVE);
|
2021-05-26 16:18:47 +02:00
|
|
|
free(buf);
|
2021-05-13 16:04:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
stream->just_underran = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = stream->local_buffer + stream->pa_offs_bytes;
|
2021-05-26 16:18:53 +02:00
|
|
|
TRACE("held: %lu, avail: %u\n", stream->pa_held_bytes, bytes);
|
2021-05-13 16:04:56 +02:00
|
|
|
bytes = min(stream->pa_held_bytes, bytes);
|
|
|
|
|
|
|
|
if (stream->pa_offs_bytes + bytes > stream->real_bufsize_bytes)
|
|
|
|
{
|
|
|
|
to_write = stream->real_bufsize_bytes - stream->pa_offs_bytes;
|
|
|
|
TRACE("writing small chunk of %u bytes\n", to_write);
|
|
|
|
write_buffer(stream, buf, to_write);
|
|
|
|
stream->pa_held_bytes -= to_write;
|
|
|
|
to_write = bytes - to_write;
|
|
|
|
stream->pa_offs_bytes = 0;
|
|
|
|
buf = stream->local_buffer;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
to_write = bytes;
|
|
|
|
|
|
|
|
TRACE("writing main chunk of %u bytes\n", to_write);
|
|
|
|
write_buffer(stream, buf, to_write);
|
|
|
|
stream->pa_offs_bytes += to_write;
|
|
|
|
stream->pa_offs_bytes %= stream->real_bufsize_bytes;
|
|
|
|
stream->pa_held_bytes -= to_write;
|
|
|
|
}
|
|
|
|
|
2021-05-13 16:05:06 +02:00
|
|
|
static void pulse_read(struct pulse_stream *stream)
|
2021-05-13 16:03:19 +02:00
|
|
|
{
|
|
|
|
size_t bytes = pa_stream_readable_size(stream->stream);
|
|
|
|
|
|
|
|
TRACE("Readable total: %zu, fragsize: %u\n", bytes, pa_stream_get_buffer_attr(stream->stream)->fragsize);
|
|
|
|
|
|
|
|
bytes += stream->peek_len - stream->peek_ofs;
|
|
|
|
|
|
|
|
while (bytes >= stream->period_bytes)
|
|
|
|
{
|
|
|
|
BYTE *dst = NULL, *src;
|
|
|
|
size_t src_len, copy, rem = stream->period_bytes;
|
|
|
|
|
|
|
|
if (stream->started)
|
|
|
|
{
|
|
|
|
LARGE_INTEGER stamp, freq;
|
|
|
|
ACPacket *p, *next;
|
|
|
|
|
|
|
|
if (!(p = (ACPacket*)list_head(&stream->packet_free_head)))
|
|
|
|
{
|
|
|
|
p = (ACPacket*)list_head(&stream->packet_filled_head);
|
|
|
|
if (!p) return;
|
|
|
|
if (!p->discont) {
|
|
|
|
next = (ACPacket*)p->entry.next;
|
|
|
|
next->discont = 1;
|
|
|
|
} else
|
|
|
|
p = (ACPacket*)list_tail(&stream->packet_filled_head);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
stream->held_bytes += stream->period_bytes;
|
|
|
|
}
|
|
|
|
NtQueryPerformanceCounter(&stamp, &freq);
|
|
|
|
p->qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart;
|
|
|
|
p->discont = 0;
|
|
|
|
list_remove(&p->entry);
|
|
|
|
list_add_tail(&stream->packet_filled_head, &p->entry);
|
|
|
|
|
|
|
|
dst = p->data;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (rem)
|
|
|
|
{
|
|
|
|
if (stream->peek_len)
|
|
|
|
{
|
|
|
|
copy = min(rem, stream->peek_len - stream->peek_ofs);
|
|
|
|
|
|
|
|
if (dst)
|
|
|
|
{
|
|
|
|
memcpy(dst, stream->peek_buffer + stream->peek_ofs, copy);
|
|
|
|
dst += copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
rem -= copy;
|
|
|
|
stream->peek_ofs += copy;
|
|
|
|
if(stream->peek_len == stream->peek_ofs)
|
|
|
|
stream->peek_len = stream->peek_ofs = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (pa_stream_peek(stream->stream, (const void**)&src, &src_len) == 0 && src_len)
|
|
|
|
{
|
|
|
|
copy = min(rem, src_len);
|
|
|
|
|
|
|
|
if (dst) {
|
|
|
|
if(src)
|
|
|
|
memcpy(dst, src, copy);
|
|
|
|
else
|
|
|
|
silence_buffer(stream->ss.format, dst, copy);
|
|
|
|
|
|
|
|
dst += copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
rem -= copy;
|
|
|
|
|
|
|
|
if (copy < src_len)
|
|
|
|
{
|
|
|
|
if (src_len > stream->peek_buffer_len)
|
|
|
|
{
|
2021-05-26 16:18:47 +02:00
|
|
|
free(stream->peek_buffer);
|
|
|
|
stream->peek_buffer = malloc(src_len);
|
2021-05-13 16:03:19 +02:00
|
|
|
stream->peek_buffer_len = src_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(src)
|
|
|
|
memcpy(stream->peek_buffer, src + copy, src_len - copy);
|
|
|
|
else
|
|
|
|
silence_buffer(stream->ss.format, stream->peek_buffer, src_len - copy);
|
|
|
|
|
|
|
|
stream->peek_len = src_len - copy;
|
|
|
|
stream->peek_ofs = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
pa_stream_drop(stream->stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bytes -= stream->period_bytes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_timer_loop(void *args)
|
2021-05-13 16:05:06 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct timer_loop_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
2021-05-13 16:05:06 +02:00
|
|
|
LARGE_INTEGER delay;
|
|
|
|
UINT32 adv_bytes;
|
|
|
|
int success;
|
|
|
|
pa_operation *o;
|
|
|
|
|
|
|
|
pulse_lock();
|
|
|
|
delay.QuadPart = -stream->mmdev_period_usec * 10;
|
|
|
|
pa_stream_get_time(stream->stream, &stream->last_time);
|
|
|
|
pulse_unlock();
|
|
|
|
|
|
|
|
while (!stream->please_quit)
|
|
|
|
{
|
|
|
|
pa_usec_t now, adv_usec = 0;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
NtDelayExecution(FALSE, &delay);
|
|
|
|
|
|
|
|
pulse_lock();
|
|
|
|
|
|
|
|
delay.QuadPart = -stream->mmdev_period_usec * 10;
|
|
|
|
|
|
|
|
o = pa_stream_update_timing_info(stream->stream, pulse_op_cb, &success);
|
|
|
|
if (o)
|
|
|
|
{
|
|
|
|
while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
|
|
|
pulse_cond_wait();
|
|
|
|
pa_operation_unref(o);
|
|
|
|
}
|
|
|
|
err = pa_stream_get_time(stream->stream, &now);
|
|
|
|
if (err == 0)
|
|
|
|
{
|
|
|
|
TRACE("got now: %s, last time: %s\n", wine_dbgstr_longlong(now), wine_dbgstr_longlong(stream->last_time));
|
|
|
|
if (stream->started && (stream->dataflow == eCapture || stream->held_bytes))
|
|
|
|
{
|
|
|
|
if(stream->just_underran)
|
|
|
|
{
|
|
|
|
stream->last_time = now;
|
|
|
|
stream->just_started = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->just_started)
|
|
|
|
{
|
|
|
|
/* let it play out a period to absorb some latency and get accurate timing */
|
|
|
|
pa_usec_t diff = now - stream->last_time;
|
|
|
|
|
|
|
|
if (diff > stream->mmdev_period_usec)
|
|
|
|
{
|
|
|
|
stream->just_started = FALSE;
|
|
|
|
stream->last_time = now;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
INT32 adjust = stream->last_time + stream->mmdev_period_usec - now;
|
|
|
|
|
|
|
|
adv_usec = now - stream->last_time;
|
|
|
|
|
|
|
|
if(adjust > ((INT32)(stream->mmdev_period_usec / 2)))
|
|
|
|
adjust = stream->mmdev_period_usec / 2;
|
|
|
|
else if(adjust < -((INT32)(stream->mmdev_period_usec / 2)))
|
|
|
|
adjust = -1 * stream->mmdev_period_usec / 2;
|
|
|
|
|
|
|
|
delay.QuadPart = -(stream->mmdev_period_usec + adjust) * 10;
|
|
|
|
|
|
|
|
stream->last_time += stream->mmdev_period_usec;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->dataflow == eRender)
|
|
|
|
{
|
|
|
|
pulse_write(stream);
|
|
|
|
|
|
|
|
/* regardless of what PA does, advance one period */
|
|
|
|
adv_bytes = min(stream->period_bytes, stream->held_bytes);
|
|
|
|
stream->lcl_offs_bytes += adv_bytes;
|
|
|
|
stream->lcl_offs_bytes %= stream->real_bufsize_bytes;
|
|
|
|
stream->held_bytes -= adv_bytes;
|
|
|
|
}
|
|
|
|
else if(stream->dataflow == eCapture)
|
|
|
|
{
|
|
|
|
pulse_read(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
stream->last_time = now;
|
|
|
|
delay.QuadPart = -stream->mmdev_period_usec * 10;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->event)
|
|
|
|
NtSetEvent(stream->event, NULL);
|
|
|
|
|
|
|
|
TRACE("%p after update, adv usec: %d, held: %u, delay usec: %u\n",
|
|
|
|
stream, (int)adv_usec,
|
|
|
|
(int)(stream->held_bytes/ pa_frame_size(&stream->ss)),
|
|
|
|
(unsigned int)(-delay.QuadPart / 10));
|
|
|
|
|
|
|
|
pulse_unlock();
|
|
|
|
}
|
2021-05-28 19:05:17 +02:00
|
|
|
|
|
|
|
return STATUS_SUCCESS;
|
2021-05-13 16:05:06 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_start(void *args)
|
2021-05-18 16:57:18 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct start_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
2021-05-18 16:57:18 +02:00
|
|
|
int success;
|
|
|
|
pa_operation *o;
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-18 16:57:18 +02:00
|
|
|
pulse_lock();
|
|
|
|
if (!pulse_stream_valid(stream))
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:57:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((stream->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) && !stream->event)
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_EVENTHANDLE_NOT_SET;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:57:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->started)
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_NOT_STOPPED;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:57:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pulse_write(stream);
|
|
|
|
|
|
|
|
if (pa_stream_is_corked(stream->stream))
|
|
|
|
{
|
|
|
|
o = pa_stream_cork(stream->stream, 0, pulse_op_cb, &success);
|
|
|
|
if (o)
|
|
|
|
{
|
|
|
|
while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
|
|
|
pulse_cond_wait();
|
|
|
|
pa_operation_unref(o);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
success = 0;
|
|
|
|
if (!success)
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = E_FAIL;
|
2021-05-18 16:57:18 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
if (SUCCEEDED(params->result))
|
2021-05-18 16:57:18 +02:00
|
|
|
{
|
|
|
|
stream->started = TRUE;
|
|
|
|
stream->just_started = TRUE;
|
|
|
|
}
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:57:18 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_stop(void *args)
|
2021-05-13 16:03:29 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct stop_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
2021-05-13 16:03:29 +02:00
|
|
|
pa_operation *o;
|
|
|
|
int success;
|
|
|
|
|
|
|
|
pulse_lock();
|
|
|
|
if (!pulse_stream_valid(stream))
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_DEVICE_INVALIDATED;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-13 16:03:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!stream->started)
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_FALSE;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-13 16:03:29 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-13 16:03:29 +02:00
|
|
|
if (stream->dataflow == eRender)
|
|
|
|
{
|
|
|
|
o = pa_stream_cork(stream->stream, 1, pulse_op_cb, &success);
|
|
|
|
if (o)
|
|
|
|
{
|
|
|
|
while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
|
|
|
pulse_cond_wait();
|
|
|
|
pa_operation_unref(o);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
success = 0;
|
|
|
|
if (!success)
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = E_FAIL;
|
2021-05-13 16:03:29 +02:00
|
|
|
}
|
2021-05-28 19:05:01 +02:00
|
|
|
if (SUCCEEDED(params->result))
|
2021-05-13 16:03:29 +02:00
|
|
|
stream->started = FALSE;
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-13 16:03:29 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_reset(void *args)
|
2021-05-18 16:58:48 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct reset_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
|
|
|
|
2021-05-18 16:58:48 +02:00
|
|
|
pulse_lock();
|
|
|
|
if (!pulse_stream_valid(stream))
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_DEVICE_INVALIDATED;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:58:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->started)
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_NOT_STOPPED;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:58:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->locked)
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_BUFFER_OPERATION_PENDING;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:58:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->dataflow == eRender)
|
|
|
|
{
|
|
|
|
/* If there is still data in the render buffer it needs to be removed from the server */
|
|
|
|
int success = 0;
|
|
|
|
if (stream->held_bytes)
|
|
|
|
{
|
|
|
|
pa_operation *o = pa_stream_flush(stream->stream, pulse_op_cb, &success);
|
|
|
|
if (o)
|
|
|
|
{
|
|
|
|
while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
|
|
|
pulse_cond_wait();
|
|
|
|
pa_operation_unref(o);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (success || !stream->held_bytes)
|
|
|
|
{
|
|
|
|
stream->clock_lastpos = stream->clock_written = 0;
|
|
|
|
stream->pa_offs_bytes = stream->lcl_offs_bytes = 0;
|
|
|
|
stream->held_bytes = stream->pa_held_bytes = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ACPacket *p;
|
|
|
|
stream->clock_written += stream->held_bytes;
|
|
|
|
stream->held_bytes = 0;
|
|
|
|
|
|
|
|
if ((p = stream->locked_ptr))
|
|
|
|
{
|
|
|
|
stream->locked_ptr = NULL;
|
|
|
|
list_add_tail(&stream->packet_free_head, &p->entry);
|
|
|
|
}
|
|
|
|
list_move_tail(&stream->packet_free_head, &stream->packet_filled_head);
|
|
|
|
}
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:58:48 +02:00
|
|
|
}
|
|
|
|
|
2021-05-26 16:18:53 +02:00
|
|
|
static BOOL alloc_tmp_buffer(struct pulse_stream *stream, SIZE_T bytes)
|
2021-05-18 16:58:55 +02:00
|
|
|
{
|
2022-01-14 17:04:17 +01:00
|
|
|
SIZE_T size;
|
|
|
|
|
2021-05-18 16:58:55 +02:00
|
|
|
if (stream->tmp_buffer_bytes >= bytes)
|
2021-05-26 16:18:53 +02:00
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
if (stream->tmp_buffer)
|
|
|
|
{
|
2022-01-14 17:04:17 +01:00
|
|
|
size = 0;
|
2021-05-26 16:18:53 +02:00
|
|
|
NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer,
|
2022-01-14 17:04:17 +01:00
|
|
|
&size, MEM_RELEASE);
|
2021-05-26 16:18:53 +02:00
|
|
|
stream->tmp_buffer = NULL;
|
|
|
|
stream->tmp_buffer_bytes = 0;
|
|
|
|
}
|
|
|
|
if (NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer,
|
|
|
|
0, &bytes, MEM_COMMIT, PAGE_READWRITE))
|
|
|
|
return FALSE;
|
2021-05-18 16:58:55 +02:00
|
|
|
|
|
|
|
stream->tmp_buffer_bytes = bytes;
|
2021-05-26 16:18:53 +02:00
|
|
|
return TRUE;
|
2021-05-18 16:58:55 +02:00
|
|
|
}
|
|
|
|
|
2021-05-19 22:24:23 +02:00
|
|
|
static UINT32 pulse_render_padding(struct pulse_stream *stream)
|
|
|
|
{
|
|
|
|
return stream->held_bytes / pa_frame_size(&stream->ss);
|
|
|
|
}
|
|
|
|
|
|
|
|
static UINT32 pulse_capture_padding(struct pulse_stream *stream)
|
|
|
|
{
|
|
|
|
ACPacket *packet = stream->locked_ptr;
|
|
|
|
if (!packet && !list_empty(&stream->packet_filled_head))
|
|
|
|
{
|
|
|
|
packet = (ACPacket*)list_head(&stream->packet_filled_head);
|
|
|
|
stream->locked_ptr = packet;
|
|
|
|
list_remove(&packet->entry);
|
|
|
|
}
|
|
|
|
return stream->held_bytes / pa_frame_size(&stream->ss);
|
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_get_render_buffer(void *args)
|
2021-05-18 16:58:55 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct get_render_buffer_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
2021-05-18 16:58:55 +02:00
|
|
|
size_t bytes;
|
|
|
|
UINT32 wri_offs_bytes;
|
|
|
|
|
|
|
|
pulse_lock();
|
|
|
|
if (!pulse_stream_valid(stream))
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_DEVICE_INVALIDATED;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:58:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->locked)
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_OUT_OF_ORDER;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:58:55 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
if (!params->frames)
|
2021-05-18 16:58:55 +02:00
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->data = NULL;
|
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:58:55 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
if (stream->held_bytes / pa_frame_size(&stream->ss) + params->frames > stream->bufsize_frames)
|
2021-05-18 16:58:55 +02:00
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_BUFFER_TOO_LARGE;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:58:55 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
bytes = params->frames * pa_frame_size(&stream->ss);
|
2021-05-18 16:58:55 +02:00
|
|
|
wri_offs_bytes = (stream->lcl_offs_bytes + stream->held_bytes) % stream->real_bufsize_bytes;
|
|
|
|
if (wri_offs_bytes + bytes > stream->real_bufsize_bytes)
|
|
|
|
{
|
2021-05-26 16:18:53 +02:00
|
|
|
if (!alloc_tmp_buffer(stream, bytes))
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = E_OUTOFMEMORY;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-26 16:18:53 +02:00
|
|
|
}
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->data = stream->tmp_buffer;
|
2021-05-18 16:58:55 +02:00
|
|
|
stream->locked = -bytes;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->data = stream->local_buffer + wri_offs_bytes;
|
2021-05-18 16:58:55 +02:00
|
|
|
stream->locked = bytes;
|
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
silence_buffer(stream->ss.format, *params->data, bytes);
|
2021-05-18 16:58:55 +02:00
|
|
|
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:58:55 +02:00
|
|
|
}
|
|
|
|
|
2021-05-18 16:59:03 +02:00
|
|
|
static void pulse_wrap_buffer(struct pulse_stream *stream, BYTE *buffer, UINT32 written_bytes)
|
|
|
|
{
|
|
|
|
UINT32 wri_offs_bytes = (stream->lcl_offs_bytes + stream->held_bytes) % stream->real_bufsize_bytes;
|
|
|
|
UINT32 chunk_bytes = stream->real_bufsize_bytes - wri_offs_bytes;
|
|
|
|
|
|
|
|
if (written_bytes <= chunk_bytes)
|
|
|
|
{
|
|
|
|
memcpy(stream->local_buffer + wri_offs_bytes, buffer, written_bytes);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memcpy(stream->local_buffer + wri_offs_bytes, buffer, chunk_bytes);
|
|
|
|
memcpy(stream->local_buffer, buffer + chunk_bytes, written_bytes - chunk_bytes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_release_render_buffer(void *args)
|
2021-05-18 16:59:03 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct release_render_buffer_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
2021-05-18 16:59:03 +02:00
|
|
|
UINT32 written_bytes;
|
|
|
|
BYTE *buffer;
|
|
|
|
|
|
|
|
pulse_lock();
|
2021-05-28 19:05:01 +02:00
|
|
|
if (!stream->locked || !params->written_frames)
|
2021-05-18 16:59:03 +02:00
|
|
|
{
|
|
|
|
stream->locked = 0;
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = params->written_frames ? AUDCLNT_E_OUT_OF_ORDER : S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:59:03 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
if (params->written_frames * pa_frame_size(&stream->ss) >
|
|
|
|
(stream->locked >= 0 ? stream->locked : -stream->locked))
|
2021-05-18 16:59:03 +02:00
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_INVALID_SIZE;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:59:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->locked >= 0)
|
|
|
|
buffer = stream->local_buffer + (stream->lcl_offs_bytes + stream->held_bytes) % stream->real_bufsize_bytes;
|
|
|
|
else
|
|
|
|
buffer = stream->tmp_buffer;
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
written_bytes = params->written_frames * pa_frame_size(&stream->ss);
|
|
|
|
if (params->flags & AUDCLNT_BUFFERFLAGS_SILENT)
|
2021-05-18 16:59:03 +02:00
|
|
|
silence_buffer(stream->ss.format, buffer, written_bytes);
|
|
|
|
|
|
|
|
if (stream->locked < 0)
|
|
|
|
pulse_wrap_buffer(stream, buffer, written_bytes);
|
|
|
|
|
|
|
|
stream->held_bytes += written_bytes;
|
|
|
|
stream->pa_held_bytes += written_bytes;
|
|
|
|
if (stream->pa_held_bytes > stream->real_bufsize_bytes)
|
|
|
|
{
|
|
|
|
stream->pa_offs_bytes += stream->pa_held_bytes - stream->real_bufsize_bytes;
|
|
|
|
stream->pa_offs_bytes %= stream->real_bufsize_bytes;
|
|
|
|
stream->pa_held_bytes = stream->real_bufsize_bytes;
|
|
|
|
}
|
|
|
|
stream->clock_written += written_bytes;
|
|
|
|
stream->locked = 0;
|
|
|
|
|
2021-12-16 18:20:26 +01:00
|
|
|
/* push as much data as we can to pulseaudio too */
|
|
|
|
pulse_write(stream);
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
TRACE("Released %u, held %lu\n", params->written_frames, stream->held_bytes / pa_frame_size(&stream->ss));
|
2021-05-18 16:59:03 +02:00
|
|
|
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:59:03 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_get_capture_buffer(void *args)
|
2021-05-19 22:27:00 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct get_capture_buffer_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
2021-05-19 22:27:00 +02:00
|
|
|
ACPacket *packet;
|
|
|
|
|
|
|
|
pulse_lock();
|
|
|
|
if (!pulse_stream_valid(stream))
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_DEVICE_INVALIDATED;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:27:00 +02:00
|
|
|
}
|
|
|
|
if (stream->locked)
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_OUT_OF_ORDER;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:27:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pulse_capture_padding(stream);
|
|
|
|
if ((packet = stream->locked_ptr))
|
|
|
|
{
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->frames = stream->period_bytes / pa_frame_size(&stream->ss);
|
|
|
|
*params->flags = 0;
|
2021-05-19 22:27:00 +02:00
|
|
|
if (packet->discont)
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->flags |= AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY;
|
|
|
|
if (params->devpos)
|
2021-05-19 22:27:00 +02:00
|
|
|
{
|
|
|
|
if (packet->discont)
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->devpos = (stream->clock_written + stream->period_bytes) / pa_frame_size(&stream->ss);
|
2021-05-19 22:27:00 +02:00
|
|
|
else
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->devpos = stream->clock_written / pa_frame_size(&stream->ss);
|
2021-05-19 22:27:00 +02:00
|
|
|
}
|
2021-05-28 19:05:01 +02:00
|
|
|
if (params->qpcpos)
|
|
|
|
*params->qpcpos = packet->qpcpos;
|
|
|
|
*params->data = packet->data;
|
2021-05-19 22:27:00 +02:00
|
|
|
}
|
|
|
|
else
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->frames = 0;
|
|
|
|
stream->locked = *params->frames;
|
2021-05-19 22:27:00 +02:00
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = *params->frames ? S_OK : AUDCLNT_S_BUFFER_EMPTY;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:27:00 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_release_capture_buffer(void *args)
|
2021-05-19 22:27:08 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct release_capture_buffer_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
|
|
|
|
2021-05-19 22:27:08 +02:00
|
|
|
pulse_lock();
|
2021-05-28 19:05:01 +02:00
|
|
|
if (!stream->locked && params->done)
|
2021-05-19 22:27:08 +02:00
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_OUT_OF_ORDER;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:27:08 +02:00
|
|
|
}
|
2021-05-28 19:05:01 +02:00
|
|
|
if (params->done && stream->locked != params->done)
|
2021-05-19 22:27:08 +02:00
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_INVALID_SIZE;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:27:08 +02:00
|
|
|
}
|
2021-05-28 19:05:01 +02:00
|
|
|
if (params->done)
|
2021-05-19 22:27:08 +02:00
|
|
|
{
|
|
|
|
ACPacket *packet = stream->locked_ptr;
|
|
|
|
stream->locked_ptr = NULL;
|
|
|
|
stream->held_bytes -= stream->period_bytes;
|
|
|
|
if (packet->discont)
|
|
|
|
stream->clock_written += 2 * stream->period_bytes;
|
|
|
|
else
|
|
|
|
stream->clock_written += stream->period_bytes;
|
|
|
|
list_add_tail(&stream->packet_free_head, &packet->entry);
|
|
|
|
}
|
|
|
|
stream->locked = 0;
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:27:08 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_get_buffer_size(void *args)
|
2021-05-19 22:24:02 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct get_buffer_size_params *params = args;
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-19 22:24:02 +02:00
|
|
|
|
|
|
|
pulse_lock();
|
2021-05-28 19:05:01 +02:00
|
|
|
if (!pulse_stream_valid(params->stream))
|
|
|
|
params->result = AUDCLNT_E_DEVICE_INVALIDATED;
|
2021-05-19 22:24:02 +02:00
|
|
|
else
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->size = params->stream->bufsize_frames;
|
2021-05-19 22:24:02 +02:00
|
|
|
pulse_unlock();
|
2021-05-28 19:05:17 +02:00
|
|
|
|
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:24:02 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_get_latency(void *args)
|
2021-05-19 22:24:13 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct get_latency_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
2021-05-19 22:24:13 +02:00
|
|
|
const pa_buffer_attr *attr;
|
|
|
|
REFERENCE_TIME lat;
|
|
|
|
|
|
|
|
pulse_lock();
|
|
|
|
if (!pulse_stream_valid(stream)) {
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_DEVICE_INVALIDATED;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:24:13 +02:00
|
|
|
}
|
|
|
|
attr = pa_stream_get_buffer_attr(stream->stream);
|
|
|
|
if (stream->dataflow == eRender)
|
|
|
|
lat = attr->minreq / pa_frame_size(&stream->ss);
|
|
|
|
else
|
|
|
|
lat = attr->fragsize / pa_frame_size(&stream->ss);
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->latency = (lat * 10000000) / stream->ss.rate + pulse_def_period[0];
|
2021-05-19 22:24:13 +02:00
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
TRACE("Latency: %u ms\n", (DWORD)(*params->latency / 10000));
|
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:24:13 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_get_current_padding(void *args)
|
2021-05-19 22:24:23 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct get_current_padding_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
|
|
|
|
2021-05-19 22:24:23 +02:00
|
|
|
pulse_lock();
|
|
|
|
if (!pulse_stream_valid(stream))
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_DEVICE_INVALIDATED;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:24:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->dataflow == eRender)
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->padding = pulse_render_padding(stream);
|
2021-05-19 22:24:23 +02:00
|
|
|
else
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->padding = pulse_capture_padding(stream);
|
2021-05-19 22:24:23 +02:00
|
|
|
pulse_unlock();
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
TRACE("%p Pad: %u ms (%u)\n", stream, muldiv(*params->padding, 1000, stream->ss.rate),
|
|
|
|
*params->padding);
|
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:24:23 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_get_next_packet_size(void *args)
|
2021-05-19 22:27:30 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct get_next_packet_size_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
|
|
|
|
2021-05-19 22:27:30 +02:00
|
|
|
pulse_lock();
|
|
|
|
pulse_capture_padding(stream);
|
|
|
|
if (stream->locked_ptr)
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->frames = stream->period_bytes / pa_frame_size(&stream->ss);
|
2021-05-19 22:27:30 +02:00
|
|
|
else
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->frames = 0;
|
2021-05-19 22:27:30 +02:00
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
|
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:27:30 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_get_frequency(void *args)
|
2021-05-19 22:27:36 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct get_frequency_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
|
|
|
|
2021-05-19 22:27:36 +02:00
|
|
|
pulse_lock();
|
|
|
|
if (!pulse_stream_valid(stream))
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_DEVICE_INVALIDATED;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:27:36 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->freq = stream->ss.rate;
|
2021-05-19 22:27:36 +02:00
|
|
|
if (stream->share == AUDCLNT_SHAREMODE_SHARED)
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->freq *= pa_frame_size(&stream->ss);
|
2021-05-19 22:27:36 +02:00
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-19 22:27:36 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_get_position(void *args)
|
2021-05-24 18:21:49 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct get_position_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
|
|
|
|
2021-05-24 18:21:49 +02:00
|
|
|
pulse_lock();
|
|
|
|
if (!pulse_stream_valid(stream))
|
|
|
|
{
|
|
|
|
pulse_unlock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = AUDCLNT_E_DEVICE_INVALIDATED;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-24 18:21:49 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->pos = stream->clock_written - stream->held_bytes;
|
2021-05-24 18:21:49 +02:00
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
if (stream->share == AUDCLNT_SHAREMODE_EXCLUSIVE || params->device)
|
|
|
|
*params->pos /= pa_frame_size(&stream->ss);
|
2021-05-24 18:21:49 +02:00
|
|
|
|
|
|
|
/* Make time never go backwards */
|
2021-05-28 19:05:01 +02:00
|
|
|
if (*params->pos < stream->clock_lastpos)
|
|
|
|
*params->pos = stream->clock_lastpos;
|
2021-05-24 18:21:49 +02:00
|
|
|
else
|
2021-05-28 19:05:01 +02:00
|
|
|
stream->clock_lastpos = *params->pos;
|
2021-05-24 18:21:49 +02:00
|
|
|
pulse_unlock();
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
TRACE("%p Position: %u\n", stream, (unsigned)*params->pos);
|
2021-05-24 18:21:49 +02:00
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
if (params->qpctime)
|
2021-05-24 18:21:49 +02:00
|
|
|
{
|
|
|
|
LARGE_INTEGER stamp, freq;
|
|
|
|
NtQueryPerformanceCounter(&stamp, &freq);
|
2021-05-28 19:05:01 +02:00
|
|
|
*params->qpctime = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart;
|
2021-05-24 18:21:49 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = S_OK;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-24 18:21:49 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_set_volumes(void *args)
|
2021-05-13 16:03:38 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct set_volumes_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
2021-05-13 16:03:38 +02:00
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < stream->ss.channels; i++)
|
2021-05-28 19:05:01 +02:00
|
|
|
stream->vol[i] = params->volumes[i] * params->master_volume * params->session_volumes[i];
|
2021-05-28 19:05:17 +02:00
|
|
|
|
|
|
|
return STATUS_SUCCESS;
|
2021-05-13 16:03:38 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_set_event_handle(void *args)
|
2021-05-18 16:59:16 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct set_event_handle_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
2021-05-18 16:59:16 +02:00
|
|
|
HRESULT hr = S_OK;
|
|
|
|
|
|
|
|
pulse_lock();
|
|
|
|
if (!pulse_stream_valid(stream))
|
|
|
|
hr = AUDCLNT_E_DEVICE_INVALIDATED;
|
|
|
|
else if (!(stream->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK))
|
|
|
|
hr = AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED;
|
|
|
|
else if (stream->event)
|
|
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
|
|
|
|
else
|
2021-05-28 19:05:01 +02:00
|
|
|
stream->event = params->event;
|
2021-05-18 16:59:16 +02:00
|
|
|
pulse_unlock();
|
|
|
|
|
2021-05-28 19:05:01 +02:00
|
|
|
params->result = hr;
|
2021-05-28 19:05:17 +02:00
|
|
|
return STATUS_SUCCESS;
|
2021-05-18 16:59:16 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 19:05:17 +02:00
|
|
|
static NTSTATUS pulse_is_started(void *args)
|
2021-05-24 18:21:56 +02:00
|
|
|
{
|
2021-05-28 19:05:17 +02:00
|
|
|
struct is_started_params *params = args;
|
2021-05-28 19:05:01 +02:00
|
|
|
struct pulse_stream *stream = params->stream;
|
|
|
|
|
2021-05-26 16:18:43 +02:00
|
|
|
pulse_lock();
|
2021-05-28 19:05:01 +02:00
|
|
|
params->started = pulse_stream_valid(stream) && stream->started;
|
2021-05-26 16:18:43 +02:00
|
|
|
pulse_unlock();
|
2021-05-28 19:05:17 +02:00
|
|
|
|
|
|
|
return STATUS_SUCCESS;
|
2021-05-24 18:21:56 +02:00
|
|
|
}
|
|
|
|
|
2021-08-10 18:02:39 +02:00
|
|
|
const unixlib_entry_t __wine_unix_call_funcs[] =
|
2021-05-11 18:29:38 +02:00
|
|
|
{
|
2021-08-10 18:02:39 +02:00
|
|
|
pulse_process_attach,
|
|
|
|
pulse_process_detach,
|
2021-05-11 18:30:17 +02:00
|
|
|
pulse_main_loop,
|
2021-05-11 18:31:45 +02:00
|
|
|
pulse_create_stream,
|
2021-05-11 18:31:34 +02:00
|
|
|
pulse_release_stream,
|
2021-05-18 16:57:18 +02:00
|
|
|
pulse_start,
|
2021-05-13 16:03:29 +02:00
|
|
|
pulse_stop,
|
2021-05-18 16:58:48 +02:00
|
|
|
pulse_reset,
|
2021-05-13 16:05:06 +02:00
|
|
|
pulse_timer_loop,
|
2021-05-18 16:58:55 +02:00
|
|
|
pulse_get_render_buffer,
|
2021-05-18 16:59:03 +02:00
|
|
|
pulse_release_render_buffer,
|
2021-05-19 22:27:00 +02:00
|
|
|
pulse_get_capture_buffer,
|
2021-05-19 22:27:08 +02:00
|
|
|
pulse_release_capture_buffer,
|
2021-05-19 22:24:02 +02:00
|
|
|
pulse_get_buffer_size,
|
2021-05-19 22:24:13 +02:00
|
|
|
pulse_get_latency,
|
2021-05-19 22:24:23 +02:00
|
|
|
pulse_get_current_padding,
|
2021-05-19 22:27:30 +02:00
|
|
|
pulse_get_next_packet_size,
|
2021-05-19 22:27:36 +02:00
|
|
|
pulse_get_frequency,
|
2021-05-24 18:21:49 +02:00
|
|
|
pulse_get_position,
|
2021-05-13 16:03:38 +02:00
|
|
|
pulse_set_volumes,
|
2021-05-18 16:59:16 +02:00
|
|
|
pulse_set_event_handle,
|
2021-05-11 18:30:00 +02:00
|
|
|
pulse_test_connect,
|
2021-05-24 18:21:56 +02:00
|
|
|
pulse_is_started,
|
2021-05-11 18:29:38 +02:00
|
|
|
};
|