mirror of
https://gitlab.winehq.org/wine/wine-staging.git
synced 2024-11-21 16:46:54 -08:00
1099 lines
35 KiB
Diff
1099 lines
35 KiB
Diff
From e3638b92aba104a74831090f0acf7779dcf055dd Mon Sep 17 00:00:00 2001
|
|
From: Maarten Lankhorst <m.b.lankhorst@gmail.com>
|
|
Date: Fri, 22 Nov 2013 21:29:57 +0100
|
|
Subject: [PATCH 11/42] winepulse: Add audioclient
|
|
|
|
---
|
|
Without AudioRenderClient and AudioCaptureClient it won't work,
|
|
but it's easier to review
|
|
---
|
|
dlls/winepulse.drv/mmdevdrv.c | 1041 ++++++++++++++++++++++++++++++++++++++++-
|
|
1 file changed, 1040 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c
|
|
index 40db26d..37d85ff 100644
|
|
--- a/dlls/winepulse.drv/mmdevdrv.c
|
|
+++ b/dlls/winepulse.drv/mmdevdrv.c
|
|
@@ -103,9 +103,55 @@ BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved)
|
|
return TRUE;
|
|
}
|
|
|
|
+typedef struct ACImpl ACImpl;
|
|
+
|
|
+typedef struct _ACPacket {
|
|
+ struct list entry;
|
|
+ UINT64 qpcpos;
|
|
+ BYTE *data;
|
|
+ UINT32 discont;
|
|
+} ACPacket;
|
|
+
|
|
+struct ACImpl {
|
|
+ IAudioClient IAudioClient_iface;
|
|
+ IAudioRenderClient IAudioRenderClient_iface;
|
|
+ IAudioCaptureClient IAudioCaptureClient_iface;
|
|
+ IAudioClock IAudioClock_iface;
|
|
+ IAudioClock2 IAudioClock2_iface;
|
|
+ IAudioStreamVolume IAudioStreamVolume_iface;
|
|
+ IMMDevice *parent;
|
|
+ struct list entry;
|
|
+ float vol[PA_CHANNELS_MAX];
|
|
+
|
|
+ LONG ref;
|
|
+ EDataFlow dataflow;
|
|
+ DWORD flags;
|
|
+ AUDCLNT_SHAREMODE share;
|
|
+ HANDLE event;
|
|
+
|
|
+ UINT32 bufsize_frames, bufsize_bytes, locked, capture_period, pad, started, peek_ofs;
|
|
+ void *locked_ptr, *tmp_buffer;
|
|
+
|
|
+ pa_stream *stream;
|
|
+ pa_sample_spec ss;
|
|
+ pa_channel_map map;
|
|
+
|
|
+ INT64 clock_lastpos, clock_written;
|
|
+ pa_usec_t clock_pulse;
|
|
+
|
|
+ struct list packet_free_head;
|
|
+ struct list packet_filled_head;
|
|
+};
|
|
|
|
static const WCHAR defaultW[] = {'P','u','l','s','e','a','u','d','i','o',0};
|
|
|
|
+static const IAudioClientVtbl AudioClient_Vtbl;
|
|
+
|
|
+static inline ACImpl *impl_from_IAudioClient(IAudioClient *iface)
|
|
+{
|
|
+ return CONTAINING_RECORD(iface, ACImpl, IAudioClient_iface);
|
|
+}
|
|
+
|
|
/* 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
|
|
@@ -351,6 +397,192 @@ static void pulse_contextcallback(pa_context *c, void *userdata) {
|
|
pthread_cond_signal(&pulse_cond);
|
|
}
|
|
|
|
+static HRESULT pulse_stream_valid(ACImpl *This) {
|
|
+ if (!This->stream)
|
|
+ return AUDCLNT_E_NOT_INITIALIZED;
|
|
+ if (!This->stream || pa_stream_get_state(This->stream) != PA_STREAM_READY)
|
|
+ return AUDCLNT_E_DEVICE_INVALIDATED;
|
|
+ return S_OK;
|
|
+}
|
|
+
|
|
+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);
|
|
+}
|
|
+
|
|
+static void pulse_op_cb(pa_stream *s, int success, void *user) {
|
|
+ TRACE("Success: %i\n", success);
|
|
+ *(int*)user = success;
|
|
+ pthread_cond_signal(&pulse_cond);
|
|
+}
|
|
+
|
|
+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_wr_callback(pa_stream *s, size_t bytes, void *userdata)
|
|
+{
|
|
+ ACImpl *This = userdata;
|
|
+ pa_usec_t time;
|
|
+ UINT32 oldpad = This->pad;
|
|
+
|
|
+ if (bytes < This->bufsize_bytes)
|
|
+ This->pad = This->bufsize_bytes - bytes;
|
|
+ else
|
|
+ This->pad = 0;
|
|
+
|
|
+ assert(oldpad >= This->pad);
|
|
+
|
|
+ if (0 && This->pad && pa_stream_get_time(This->stream, &time) >= 0)
|
|
+ This->clock_pulse = time;
|
|
+ else
|
|
+ This->clock_pulse = PA_USEC_INVALID;
|
|
+
|
|
+ This->clock_written += oldpad - This->pad;
|
|
+ TRACE("New pad: %u (-%u)\n", This->pad / pa_frame_size(&This->ss), (oldpad - This->pad) / pa_frame_size(&This->ss));
|
|
+
|
|
+ if (This->event)
|
|
+ SetEvent(This->event);
|
|
+}
|
|
+
|
|
+static void pulse_underflow_callback(pa_stream *s, void *userdata)
|
|
+{
|
|
+ ACImpl *This = userdata;
|
|
+ This->clock_pulse = PA_USEC_INVALID;
|
|
+ WARN("Underflow\n");
|
|
+}
|
|
+
|
|
+/* Latency is periodically updated even when nothing is played,
|
|
+ * because of PA_STREAM_AUTO_TIMING_UPDATE so use it as timer
|
|
+ *
|
|
+ * Perfect for passing all tests :)
|
|
+ */
|
|
+static void pulse_latency_callback(pa_stream *s, void *userdata)
|
|
+{
|
|
+ ACImpl *This = userdata;
|
|
+ if (!This->pad && This->event)
|
|
+ SetEvent(This->event);
|
|
+}
|
|
+
|
|
+static void pulse_started_callback(pa_stream *s, void *userdata)
|
|
+{
|
|
+ ACImpl *This = userdata;
|
|
+ pa_usec_t time;
|
|
+
|
|
+ TRACE("(Re)started playing\n");
|
|
+ assert(This->clock_pulse == PA_USEC_INVALID);
|
|
+ if (0 && pa_stream_get_time(This->stream, &time) >= 0)
|
|
+ This->clock_pulse = time;
|
|
+ if (This->event)
|
|
+ SetEvent(This->event);
|
|
+}
|
|
+
|
|
+static void pulse_rd_loop(ACImpl *This, size_t bytes)
|
|
+{
|
|
+ while (bytes >= This->capture_period) {
|
|
+ ACPacket *p, *next;
|
|
+ LARGE_INTEGER stamp, freq;
|
|
+ BYTE *dst, *src;
|
|
+ UINT32 src_len, copy, rem = This->capture_period;
|
|
+ if (!(p = (ACPacket*)list_head(&This->packet_free_head))) {
|
|
+ p = (ACPacket*)list_head(&This->packet_filled_head);
|
|
+ if (!p->discont) {
|
|
+ next = (ACPacket*)p->entry.next;
|
|
+ next->discont = 1;
|
|
+ } else
|
|
+ p = (ACPacket*)list_tail(&This->packet_filled_head);
|
|
+ assert(This->pad == This->bufsize_bytes);
|
|
+ } else {
|
|
+ assert(This->pad < This->bufsize_bytes);
|
|
+ This->pad += This->capture_period;
|
|
+ assert(This->pad <= This->bufsize_bytes);
|
|
+ }
|
|
+ QueryPerformanceCounter(&stamp);
|
|
+ QueryPerformanceFrequency(&freq);
|
|
+ p->qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart;
|
|
+ p->discont = 0;
|
|
+ list_remove(&p->entry);
|
|
+ list_add_tail(&This->packet_filled_head, &p->entry);
|
|
+
|
|
+ dst = p->data;
|
|
+ while (rem) {
|
|
+ pa_stream_peek(This->stream, (const void**)&src, &src_len);
|
|
+ assert(src_len && src_len <= bytes);
|
|
+ assert(This->peek_ofs < src_len);
|
|
+ src += This->peek_ofs;
|
|
+ src_len -= This->peek_ofs;
|
|
+
|
|
+ copy = rem;
|
|
+ if (copy > src_len)
|
|
+ copy = src_len;
|
|
+ memcpy(dst, src, rem);
|
|
+ src += copy;
|
|
+ src_len -= copy;
|
|
+ dst += copy;
|
|
+ rem -= copy;
|
|
+
|
|
+ if (!src_len) {
|
|
+ This->peek_ofs = 0;
|
|
+ pa_stream_drop(This->stream);
|
|
+ } else
|
|
+ This->peek_ofs += copy;
|
|
+ }
|
|
+ bytes -= This->capture_period;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void pulse_rd_drop(ACImpl *This, size_t bytes)
|
|
+{
|
|
+ while (bytes >= This->capture_period) {
|
|
+ UINT32 src_len, copy, rem = This->capture_period;
|
|
+ while (rem) {
|
|
+ const void *src;
|
|
+ pa_stream_peek(This->stream, &src, &src_len);
|
|
+ assert(src_len && src_len <= bytes);
|
|
+ assert(This->peek_ofs < src_len);
|
|
+ src_len -= This->peek_ofs;
|
|
+
|
|
+ copy = rem;
|
|
+ if (copy > src_len)
|
|
+ copy = src_len;
|
|
+
|
|
+ src_len -= copy;
|
|
+ rem -= copy;
|
|
+
|
|
+ if (!src_len) {
|
|
+ This->peek_ofs = 0;
|
|
+ pa_stream_drop(This->stream);
|
|
+ } else
|
|
+ This->peek_ofs += copy;
|
|
+ bytes -= copy;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void pulse_rd_callback(pa_stream *s, size_t bytes, void *userdata)
|
|
+{
|
|
+ ACImpl *This = userdata;
|
|
+
|
|
+ TRACE("Readable total: %u, fragsize: %u\n", bytes, pa_stream_get_buffer_attr(s)->fragsize);
|
|
+ assert(bytes >= This->peek_ofs);
|
|
+ bytes -= This->peek_ofs;
|
|
+ if (bytes < This->capture_period)
|
|
+ return;
|
|
+
|
|
+ if (This->started)
|
|
+ pulse_rd_loop(This, bytes);
|
|
+ else
|
|
+ pulse_rd_drop(This, bytes);
|
|
+
|
|
+ if (This->event)
|
|
+ SetEvent(This->event);
|
|
+}
|
|
+
|
|
static void pulse_stream_state(pa_stream *s, void *user)
|
|
{
|
|
pa_stream_state_t state = pa_stream_get_state(s);
|
|
@@ -358,6 +590,53 @@ static void pulse_stream_state(pa_stream *s, void *user)
|
|
pthread_cond_signal(&pulse_cond);
|
|
}
|
|
|
|
+static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
|
|
+ int ret;
|
|
+ char buffer[64];
|
|
+ static LONG number;
|
|
+ pa_buffer_attr attr;
|
|
+ if (This->stream) {
|
|
+ pa_stream_disconnect(This->stream);
|
|
+ while (pa_stream_get_state(This->stream) == PA_STREAM_READY)
|
|
+ pthread_cond_wait(&pulse_cond, &pulse_lock);
|
|
+ pa_stream_unref(This->stream);
|
|
+ }
|
|
+ ret = InterlockedIncrement(&number);
|
|
+ sprintf(buffer, "audio stream #%i", ret);
|
|
+ This->stream = pa_stream_new(pulse_ctx, buffer, &This->ss, &This->map);
|
|
+ pa_stream_set_state_callback(This->stream, pulse_stream_state, This);
|
|
+ pa_stream_set_buffer_attr_callback(This->stream, pulse_attr_update, This);
|
|
+ pa_stream_set_moved_callback(This->stream, pulse_attr_update, This);
|
|
+
|
|
+ /* Pulseaudio will fill in correct values */
|
|
+ attr.minreq = attr.fragsize = period_bytes;
|
|
+ attr.maxlength = attr.tlength = This->bufsize_bytes;
|
|
+ attr.prebuf = pa_frame_size(&This->ss);
|
|
+ dump_attr(&attr);
|
|
+ if (This->dataflow == eRender)
|
|
+ ret = pa_stream_connect_playback(This->stream, NULL, &attr,
|
|
+ PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS, NULL, NULL);
|
|
+ else
|
|
+ ret = pa_stream_connect_record(This->stream, NULL, &attr,
|
|
+ PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS);
|
|
+ if (ret < 0) {
|
|
+ WARN("Returns %i\n", ret);
|
|
+ return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
|
|
+ }
|
|
+ while (pa_stream_get_state(This->stream) == PA_STREAM_CREATING)
|
|
+ pthread_cond_wait(&pulse_cond, &pulse_lock);
|
|
+ if (pa_stream_get_state(This->stream) != PA_STREAM_READY)
|
|
+ return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
|
|
+
|
|
+ if (This->dataflow == eRender) {
|
|
+ pa_stream_set_write_callback(This->stream, pulse_wr_callback, This);
|
|
+ pa_stream_set_underflow_callback(This->stream, pulse_underflow_callback, This);
|
|
+ pa_stream_set_started_callback(This->stream, pulse_started_callback, This);
|
|
+ } else
|
|
+ pa_stream_set_read_callback(This->stream, pulse_rd_callback, This);
|
|
+ return S_OK;
|
|
+}
|
|
+
|
|
HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids, void ***keys,
|
|
UINT *num, UINT *def_index)
|
|
{
|
|
@@ -402,14 +681,774 @@ int WINAPI AUDDRV_GetPriority(void)
|
|
HRESULT WINAPI AUDDRV_GetAudioEndpoint(void *key, IMMDevice *dev,
|
|
EDataFlow dataflow, IAudioClient **out)
|
|
{
|
|
+ HRESULT hr;
|
|
+ ACImpl *This;
|
|
+ int i;
|
|
+
|
|
TRACE("%p %p %d %p\n", key, dev, dataflow, out);
|
|
if (dataflow != eRender && dataflow != eCapture)
|
|
return E_UNEXPECTED;
|
|
|
|
*out = NULL;
|
|
- return E_NOTIMPL;
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ hr = pulse_connect();
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ if (FAILED(hr))
|
|
+ return hr;
|
|
+
|
|
+ This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This));
|
|
+ if (!This)
|
|
+ return E_OUTOFMEMORY;
|
|
+
|
|
+ This->IAudioClient_iface.lpVtbl = &AudioClient_Vtbl;
|
|
+ This->dataflow = dataflow;
|
|
+ This->parent = dev;
|
|
+ This->clock_pulse = PA_USEC_INVALID;
|
|
+ for (i = 0; i < PA_CHANNELS_MAX; ++i)
|
|
+ This->vol[i] = 1.f;
|
|
+ IMMDevice_AddRef(This->parent);
|
|
+
|
|
+ *out = &This->IAudioClient_iface;
|
|
+ IAudioClient_AddRef(&This->IAudioClient_iface);
|
|
+
|
|
+ return S_OK;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_QueryInterface(IAudioClient *iface,
|
|
+ REFIID riid, void **ppv)
|
|
+{
|
|
+ TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
|
|
+
|
|
+ if (!ppv)
|
|
+ return E_POINTER;
|
|
+ *ppv = NULL;
|
|
+ if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClient))
|
|
+ *ppv = iface;
|
|
+ if (*ppv) {
|
|
+ IUnknown_AddRef((IUnknown*)*ppv);
|
|
+ return S_OK;
|
|
+ }
|
|
+ WARN("Unknown interface %s\n", debugstr_guid(riid));
|
|
+ return E_NOINTERFACE;
|
|
+}
|
|
+
|
|
+static ULONG WINAPI AudioClient_AddRef(IAudioClient *iface)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ ULONG ref;
|
|
+ ref = InterlockedIncrement(&This->ref);
|
|
+ TRACE("(%p) Refcount now %u\n", This, ref);
|
|
+ return ref;
|
|
+}
|
|
+
|
|
+static ULONG WINAPI AudioClient_Release(IAudioClient *iface)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ ULONG ref;
|
|
+ ref = InterlockedDecrement(&This->ref);
|
|
+ TRACE("(%p) Refcount now %u\n", This, ref);
|
|
+ if (!ref) {
|
|
+ if (This->stream) {
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ if (PA_STREAM_IS_GOOD(pa_stream_get_state(This->stream))) {
|
|
+ pa_stream_disconnect(This->stream);
|
|
+ while (PA_STREAM_IS_GOOD(pa_stream_get_state(This->stream)))
|
|
+ pthread_cond_wait(&pulse_cond, &pulse_lock);
|
|
+ }
|
|
+ pa_stream_unref(This->stream);
|
|
+ This->stream = NULL;
|
|
+ list_remove(&This->entry);
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ }
|
|
+ IMMDevice_Release(This->parent);
|
|
+ HeapFree(GetProcessHeap(), 0, This->tmp_buffer);
|
|
+ HeapFree(GetProcessHeap(), 0, This);
|
|
+ }
|
|
+ return ref;
|
|
+}
|
|
+
|
|
+static void dump_fmt(const WAVEFORMATEX *fmt)
|
|
+{
|
|
+ TRACE("wFormatTag: 0x%x (", fmt->wFormatTag);
|
|
+ switch(fmt->wFormatTag) {
|
|
+ case WAVE_FORMAT_PCM:
|
|
+ TRACE("WAVE_FORMAT_PCM");
|
|
+ break;
|
|
+ case WAVE_FORMAT_IEEE_FLOAT:
|
|
+ TRACE("WAVE_FORMAT_IEEE_FLOAT");
|
|
+ break;
|
|
+ case WAVE_FORMAT_EXTENSIBLE:
|
|
+ TRACE("WAVE_FORMAT_EXTENSIBLE");
|
|
+ break;
|
|
+ default:
|
|
+ TRACE("Unknown");
|
|
+ break;
|
|
+ }
|
|
+ TRACE(")\n");
|
|
+
|
|
+ TRACE("nChannels: %u\n", fmt->nChannels);
|
|
+ TRACE("nSamplesPerSec: %u\n", fmt->nSamplesPerSec);
|
|
+ TRACE("nAvgBytesPerSec: %u\n", fmt->nAvgBytesPerSec);
|
|
+ TRACE("nBlockAlign: %u\n", fmt->nBlockAlign);
|
|
+ TRACE("wBitsPerSample: %u\n", fmt->wBitsPerSample);
|
|
+ TRACE("cbSize: %u\n", fmt->cbSize);
|
|
+
|
|
+ if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
|
+ WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt;
|
|
+ TRACE("dwChannelMask: %08x\n", fmtex->dwChannelMask);
|
|
+ TRACE("Samples: %04x\n", fmtex->Samples.wReserved);
|
|
+ TRACE("SubFormat: %s\n", wine_dbgstr_guid(&fmtex->SubFormat));
|
|
+ }
|
|
+}
|
|
+
|
|
+static WAVEFORMATEX *clone_format(const WAVEFORMATEX *fmt)
|
|
+{
|
|
+ WAVEFORMATEX *ret;
|
|
+ size_t size;
|
|
+
|
|
+ if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
|
+ size = sizeof(WAVEFORMATEXTENSIBLE);
|
|
+ else
|
|
+ size = sizeof(WAVEFORMATEX);
|
|
+
|
|
+ ret = CoTaskMemAlloc(size);
|
|
+ if (!ret)
|
|
+ return NULL;
|
|
+
|
|
+ memcpy(ret, fmt, size);
|
|
+
|
|
+ ret->cbSize = size - sizeof(WAVEFORMATEX);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static DWORD get_channel_mask(unsigned int channels)
|
|
+{
|
|
+ switch(channels) {
|
|
+ case 0:
|
|
+ return 0;
|
|
+ case 1:
|
|
+ return SPEAKER_FRONT_CENTER;
|
|
+ case 2:
|
|
+ return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
|
+ case 3:
|
|
+ return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
|
|
+ SPEAKER_LOW_FREQUENCY;
|
|
+ case 4:
|
|
+ return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT |
|
|
+ SPEAKER_BACK_RIGHT;
|
|
+ case 5:
|
|
+ return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT |
|
|
+ SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY;
|
|
+ case 6:
|
|
+ return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT |
|
|
+ SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_FRONT_CENTER;
|
|
+ case 7:
|
|
+ return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT |
|
|
+ SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_FRONT_CENTER |
|
|
+ SPEAKER_BACK_CENTER;
|
|
+ case 8:
|
|
+ return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT |
|
|
+ SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_FRONT_CENTER |
|
|
+ SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
|
|
+ }
|
|
+ FIXME("Unknown speaker configuration: %u\n", channels);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static HRESULT pulse_spec_from_waveformat(ACImpl *This, const WAVEFORMATEX *fmt)
|
|
+{
|
|
+ pa_channel_map_init(&This->map);
|
|
+ This->ss.rate = fmt->nSamplesPerSec;
|
|
+ This->ss.format = PA_SAMPLE_INVALID;
|
|
+ switch(fmt->wFormatTag) {
|
|
+ case WAVE_FORMAT_IEEE_FLOAT:
|
|
+ if (!fmt->nChannels || fmt->nChannels > 2 || fmt->wBitsPerSample != 32)
|
|
+ break;
|
|
+ This->ss.format = PA_SAMPLE_FLOAT32LE;
|
|
+ pa_channel_map_init_auto(&This->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA);
|
|
+ break;
|
|
+ case WAVE_FORMAT_PCM:
|
|
+ if (!fmt->nChannels || fmt->nChannels > 2)
|
|
+ break;
|
|
+ if (fmt->wBitsPerSample == 8)
|
|
+ This->ss.format = PA_SAMPLE_U8;
|
|
+ else if (fmt->wBitsPerSample == 16)
|
|
+ This->ss.format = PA_SAMPLE_S16LE;
|
|
+ pa_channel_map_init_auto(&This->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)
|
|
+ This->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)
|
|
+ This->ss.format = PA_SAMPLE_U8;
|
|
+ break;
|
|
+ case 16:
|
|
+ if (valid == 16)
|
|
+ This->ss.format = PA_SAMPLE_S16LE;
|
|
+ break;
|
|
+ case 24:
|
|
+ if (valid == 24)
|
|
+ This->ss.format = PA_SAMPLE_S24LE;
|
|
+ break;
|
|
+ case 32:
|
|
+ if (valid == 24)
|
|
+ This->ss.format = PA_SAMPLE_S24_32LE;
|
|
+ else if (valid == 32)
|
|
+ This->ss.format = PA_SAMPLE_S32LE;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ This->map.channels = fmt->nChannels;
|
|
+ if (!mask)
|
|
+ mask = get_channel_mask(fmt->nChannels);
|
|
+ for (j = 0; j < sizeof(pulse_pos_from_wfx)/sizeof(*pulse_pos_from_wfx) && i < fmt->nChannels; ++j) {
|
|
+ if (mask & (1 << j))
|
|
+ This->map.map[i++] = pulse_pos_from_wfx[j];
|
|
+ }
|
|
+
|
|
+ /* Special case for mono since pulse appears to map it differently */
|
|
+ if (mask == SPEAKER_FRONT_CENTER)
|
|
+ This->map.map[0] = PA_CHANNEL_POSITION_MONO;
|
|
+
|
|
+ if ((mask & SPEAKER_ALL) && i < fmt->nChannels) {
|
|
+ This->map.map[i++] = PA_CHANNEL_POSITION_MONO;
|
|
+ FIXME("Is the 'all' channel mapped correctly?\n");
|
|
+ }
|
|
+
|
|
+ if (i < fmt->nChannels || (mask & SPEAKER_RESERVED)) {
|
|
+ This->map.channels = 0;
|
|
+ ERR("Invalid channel mask: %i/%i and %x\n", i, fmt->nChannels, mask);
|
|
+ break;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ default: FIXME("Unhandled tag %x\n", fmt->wFormatTag);
|
|
+ }
|
|
+ This->ss.channels = This->map.channels;
|
|
+ if (!pa_channel_map_valid(&This->map) || This->ss.format == PA_SAMPLE_INVALID) {
|
|
+ ERR("Invalid format! Channel spec valid: %i, format: %i\n", pa_channel_map_valid(&This->map), This->ss.format);
|
|
+ dump_fmt(fmt);
|
|
+ return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
|
+ }
|
|
+ return S_OK;
|
|
}
|
|
|
|
+static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
|
|
+ AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration,
|
|
+ REFERENCE_TIME period, const WAVEFORMATEX *fmt,
|
|
+ const GUID *sessionguid)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ HRESULT hr = S_OK;
|
|
+ UINT period_bytes;
|
|
+
|
|
+ TRACE("(%p)->(%x, %x, %s, %s, %p, %s)\n", This, mode, flags,
|
|
+ wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid));
|
|
+
|
|
+ if (!fmt)
|
|
+ return E_POINTER;
|
|
+
|
|
+ if (mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE)
|
|
+ return AUDCLNT_E_NOT_INITIALIZED;
|
|
+ if (mode == AUDCLNT_SHAREMODE_EXCLUSIVE)
|
|
+ return AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED;
|
|
+
|
|
+ if (flags & ~(AUDCLNT_STREAMFLAGS_CROSSPROCESS |
|
|
+ AUDCLNT_STREAMFLAGS_LOOPBACK |
|
|
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
|
|
+ AUDCLNT_STREAMFLAGS_NOPERSIST |
|
|
+ AUDCLNT_STREAMFLAGS_RATEADJUST |
|
|
+ AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED |
|
|
+ AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE |
|
|
+ AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED)) {
|
|
+ TRACE("Unknown flags: %08x\n", flags);
|
|
+ return E_INVALIDARG;
|
|
+ }
|
|
+
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ if (This->stream) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return AUDCLNT_E_ALREADY_INITIALIZED;
|
|
+ }
|
|
+
|
|
+ hr = pulse_spec_from_waveformat(This, fmt);
|
|
+ if (FAILED(hr))
|
|
+ goto exit;
|
|
+
|
|
+ if (mode == AUDCLNT_SHAREMODE_SHARED) {
|
|
+ period = pulse_def_period[This->dataflow == eCapture];
|
|
+ if (duration < 2 * period)
|
|
+ duration = 2 * period;
|
|
+ }
|
|
+ period_bytes = pa_frame_size(&This->ss) * MulDiv(period, This->ss.rate, 10000000);
|
|
+
|
|
+ if (duration < 20000000)
|
|
+ This->bufsize_frames = ceil((duration / 10000000.) * fmt->nSamplesPerSec);
|
|
+ else
|
|
+ This->bufsize_frames = 2 * fmt->nSamplesPerSec;
|
|
+ This->bufsize_bytes = This->bufsize_frames * pa_frame_size(&This->ss);
|
|
+
|
|
+ This->share = mode;
|
|
+ This->flags = flags;
|
|
+ hr = pulse_stream_connect(This, period_bytes);
|
|
+ if (SUCCEEDED(hr)) {
|
|
+ UINT32 unalign;
|
|
+ const pa_buffer_attr *attr = pa_stream_get_buffer_attr(This->stream);
|
|
+ /* Update frames according to new size */
|
|
+ dump_attr(attr);
|
|
+ if (This->dataflow == eRender)
|
|
+ This->bufsize_bytes = attr->tlength;
|
|
+ else {
|
|
+ This->capture_period = period_bytes = attr->fragsize;
|
|
+ if ((unalign = This->bufsize_bytes % period_bytes))
|
|
+ This->bufsize_bytes += period_bytes - unalign;
|
|
+ }
|
|
+ This->bufsize_frames = This->bufsize_bytes / pa_frame_size(&This->ss);
|
|
+ }
|
|
+ if (SUCCEEDED(hr)) {
|
|
+ UINT32 i, capture_packets = This->capture_period ? This->bufsize_bytes / This->capture_period : 0;
|
|
+ This->tmp_buffer = HeapAlloc(GetProcessHeap(), 0, This->bufsize_bytes + capture_packets * sizeof(ACPacket));
|
|
+ if (!This->tmp_buffer)
|
|
+ hr = E_OUTOFMEMORY;
|
|
+ else {
|
|
+ ACPacket *cur_packet = (ACPacket*)((char*)This->tmp_buffer + This->bufsize_bytes);
|
|
+ BYTE *data = This->tmp_buffer;
|
|
+ memset(This->tmp_buffer, This->ss.format == PA_SAMPLE_U8 ? 0x80 : 0, This->bufsize_bytes);
|
|
+ list_init(&This->packet_free_head);
|
|
+ list_init(&This->packet_filled_head);
|
|
+ for (i = 0; i < capture_packets; ++i, ++cur_packet) {
|
|
+ list_add_tail(&This->packet_free_head, &cur_packet->entry);
|
|
+ cur_packet->data = data;
|
|
+ data += This->capture_period;
|
|
+ }
|
|
+ assert(!This->capture_period || This->bufsize_bytes == This->capture_period * capture_packets);
|
|
+ assert(!capture_packets || data - This->bufsize_bytes == This->tmp_buffer);
|
|
+ }
|
|
+ }
|
|
+
|
|
+exit:
|
|
+ if (FAILED(hr)) {
|
|
+ HeapFree(GetProcessHeap(), 0, This->tmp_buffer);
|
|
+ This->tmp_buffer = NULL;
|
|
+ if (This->stream) {
|
|
+ pa_stream_disconnect(This->stream);
|
|
+ pa_stream_unref(This->stream);
|
|
+ This->stream = NULL;
|
|
+ }
|
|
+ }
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return hr;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_GetBufferSize(IAudioClient *iface,
|
|
+ UINT32 *out)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ HRESULT hr;
|
|
+
|
|
+ TRACE("(%p)->(%p)\n", This, out);
|
|
+
|
|
+ if (!out)
|
|
+ return E_POINTER;
|
|
+
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ hr = pulse_stream_valid(This);
|
|
+ if (SUCCEEDED(hr))
|
|
+ *out = This->bufsize_frames;
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+
|
|
+ return hr;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient *iface,
|
|
+ REFERENCE_TIME *latency)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ const pa_buffer_attr *attr;
|
|
+ REFERENCE_TIME lat;
|
|
+ HRESULT hr;
|
|
+
|
|
+ TRACE("(%p)->(%p)\n", This, latency);
|
|
+
|
|
+ if (!latency)
|
|
+ return E_POINTER;
|
|
+
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ hr = pulse_stream_valid(This);
|
|
+ if (FAILED(hr)) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return hr;
|
|
+ }
|
|
+ attr = pa_stream_get_buffer_attr(This->stream);
|
|
+ if (This->dataflow == eRender)
|
|
+ lat = attr->minreq / pa_frame_size(&This->ss);
|
|
+ else
|
|
+ lat = attr->fragsize / pa_frame_size(&This->ss);
|
|
+ *latency = 10000000;
|
|
+ *latency *= lat;
|
|
+ *latency /= This->ss.rate;
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ TRACE("Latency: %u ms\n", (DWORD)(*latency / 10000));
|
|
+ return S_OK;
|
|
+}
|
|
+
|
|
+static void ACImpl_GetRenderPad(ACImpl *This, UINT32 *out)
|
|
+{
|
|
+ *out = This->pad / pa_frame_size(&This->ss);
|
|
+}
|
|
+
|
|
+static void ACImpl_GetCapturePad(ACImpl *This, UINT32 *out)
|
|
+{
|
|
+ ACPacket *packet = This->locked_ptr;
|
|
+ if (!packet && !list_empty(&This->packet_filled_head)) {
|
|
+ packet = (ACPacket*)list_head(&This->packet_filled_head);
|
|
+ This->locked_ptr = packet;
|
|
+ list_remove(&packet->entry);
|
|
+ }
|
|
+ if (out)
|
|
+ *out = This->pad / pa_frame_size(&This->ss);
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient *iface,
|
|
+ UINT32 *out)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ HRESULT hr;
|
|
+
|
|
+ TRACE("(%p)->(%p)\n", This, out);
|
|
+
|
|
+ if (!out)
|
|
+ return E_POINTER;
|
|
+
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ hr = pulse_stream_valid(This);
|
|
+ if (FAILED(hr)) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return hr;
|
|
+ }
|
|
+
|
|
+ if (This->dataflow == eRender)
|
|
+ ACImpl_GetRenderPad(This, out);
|
|
+ else
|
|
+ ACImpl_GetCapturePad(This, out);
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+
|
|
+ TRACE("%p Pad: %u ms (%u)\n", This, MulDiv(*out, 1000, This->ss.rate), *out);
|
|
+ return S_OK;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient *iface,
|
|
+ AUDCLNT_SHAREMODE mode, const WAVEFORMATEX *fmt,
|
|
+ WAVEFORMATEX **out)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ HRESULT hr = S_OK;
|
|
+ WAVEFORMATEX *closest = NULL;
|
|
+
|
|
+ TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out);
|
|
+
|
|
+ if (!fmt || (mode == AUDCLNT_SHAREMODE_SHARED && !out))
|
|
+ return E_POINTER;
|
|
+
|
|
+ if (out)
|
|
+ *out = NULL;
|
|
+ if (mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE)
|
|
+ return E_INVALIDARG;
|
|
+ if (mode == AUDCLNT_SHAREMODE_EXCLUSIVE)
|
|
+ return This->dataflow == eCapture ? AUDCLNT_E_UNSUPPORTED_FORMAT : AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED;
|
|
+ if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
|
|
+ fmt->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX))
|
|
+ return E_INVALIDARG;
|
|
+
|
|
+ dump_fmt(fmt);
|
|
+
|
|
+ closest = clone_format(fmt);
|
|
+ if (!closest)
|
|
+ hr = E_OUTOFMEMORY;
|
|
+
|
|
+ if (hr == S_OK || !out) {
|
|
+ CoTaskMemFree(closest);
|
|
+ if (out)
|
|
+ *out = NULL;
|
|
+ } else if (closest) {
|
|
+ closest->nBlockAlign =
|
|
+ closest->nChannels * closest->wBitsPerSample / 8;
|
|
+ closest->nAvgBytesPerSec =
|
|
+ closest->nBlockAlign * closest->nSamplesPerSec;
|
|
+ *out = closest;
|
|
+ }
|
|
+
|
|
+ TRACE("returning: %08x %p\n", hr, out ? *out : NULL);
|
|
+ return hr;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient *iface,
|
|
+ WAVEFORMATEX **pwfx)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ WAVEFORMATEXTENSIBLE *fmt = &pulse_fmt[This->dataflow == eCapture];
|
|
+
|
|
+ TRACE("(%p)->(%p)\n", This, pwfx);
|
|
+
|
|
+ if (!pwfx)
|
|
+ return E_POINTER;
|
|
+
|
|
+ *pwfx = clone_format(&fmt->Format);
|
|
+ if (!*pwfx)
|
|
+ return E_OUTOFMEMORY;
|
|
+ dump_fmt(*pwfx);
|
|
+ return S_OK;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient *iface,
|
|
+ REFERENCE_TIME *defperiod, REFERENCE_TIME *minperiod)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+
|
|
+ TRACE("(%p)->(%p, %p)\n", This, defperiod, minperiod);
|
|
+
|
|
+ if (!defperiod && !minperiod)
|
|
+ return E_POINTER;
|
|
+
|
|
+ if (defperiod)
|
|
+ *defperiod = pulse_def_period[This->dataflow == eCapture];
|
|
+ if (minperiod)
|
|
+ *minperiod = pulse_min_period[This->dataflow == eCapture];
|
|
+
|
|
+ return S_OK;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_Start(IAudioClient *iface)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ HRESULT hr = S_OK;
|
|
+ int success;
|
|
+ pa_operation *o;
|
|
+
|
|
+ TRACE("(%p)\n", This);
|
|
+
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ hr = pulse_stream_valid(This);
|
|
+ if (FAILED(hr)) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return hr;
|
|
+ }
|
|
+
|
|
+ if ((This->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) && !This->event) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return AUDCLNT_E_EVENTHANDLE_NOT_SET;
|
|
+ }
|
|
+
|
|
+ if (This->started) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return AUDCLNT_E_NOT_STOPPED;
|
|
+ }
|
|
+ This->clock_pulse = PA_USEC_INVALID;
|
|
+
|
|
+ if (pa_stream_is_corked(This->stream)) {
|
|
+ o = pa_stream_cork(This->stream, 0, pulse_op_cb, &success);
|
|
+ if (o) {
|
|
+ while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
|
+ pthread_cond_wait(&pulse_cond, &pulse_lock);
|
|
+ pa_operation_unref(o);
|
|
+ } else
|
|
+ success = 0;
|
|
+ if (!success)
|
|
+ hr = E_FAIL;
|
|
+ }
|
|
+ if (SUCCEEDED(hr)) {
|
|
+ This->started = TRUE;
|
|
+ if (This->dataflow == eRender && This->event)
|
|
+ pa_stream_set_latency_update_callback(This->stream, pulse_latency_callback, This);
|
|
+ }
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return hr;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_Stop(IAudioClient *iface)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ HRESULT hr = S_OK;
|
|
+ pa_operation *o;
|
|
+ int success;
|
|
+
|
|
+ TRACE("(%p)\n", This);
|
|
+
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ hr = pulse_stream_valid(This);
|
|
+ if (FAILED(hr)) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return hr;
|
|
+ }
|
|
+
|
|
+ if (!This->started) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return S_FALSE;
|
|
+ }
|
|
+
|
|
+ if (This->dataflow == eRender) {
|
|
+ o = pa_stream_cork(This->stream, 1, pulse_op_cb, &success);
|
|
+ if (o) {
|
|
+ while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
|
+ pthread_cond_wait(&pulse_cond, &pulse_lock);
|
|
+ pa_operation_unref(o);
|
|
+ } else
|
|
+ success = 0;
|
|
+ if (!success)
|
|
+ hr = E_FAIL;
|
|
+ }
|
|
+ if (SUCCEEDED(hr)) {
|
|
+ This->started = FALSE;
|
|
+ This->clock_pulse = PA_USEC_INVALID;
|
|
+ }
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return hr;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ HRESULT hr = S_OK;
|
|
+
|
|
+ TRACE("(%p)\n", This);
|
|
+
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ hr = pulse_stream_valid(This);
|
|
+ if (FAILED(hr)) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return hr;
|
|
+ }
|
|
+
|
|
+ if (This->started) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return AUDCLNT_E_NOT_STOPPED;
|
|
+ }
|
|
+
|
|
+ if (This->locked) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return AUDCLNT_E_BUFFER_OPERATION_PENDING;
|
|
+ }
|
|
+
|
|
+ if (This->dataflow == eRender) {
|
|
+ /* If there is still data in the render buffer it needs to be removed from the server */
|
|
+ int success = 0;
|
|
+ if (This->pad) {
|
|
+ pa_operation *o = pa_stream_flush(This->stream, pulse_op_cb, &success);
|
|
+ if (o) {
|
|
+ while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
|
|
+ pthread_cond_wait(&pulse_cond, &pulse_lock);
|
|
+ pa_operation_unref(o);
|
|
+ }
|
|
+ }
|
|
+ if (success || !This->pad)
|
|
+ This->clock_lastpos = This->clock_written = This->pad = 0;
|
|
+ } else {
|
|
+ ACPacket *p;
|
|
+ This->clock_written += This->pad;
|
|
+ This->pad = 0;
|
|
+
|
|
+ if ((p = This->locked_ptr)) {
|
|
+ This->locked_ptr = NULL;
|
|
+ list_add_tail(&This->packet_free_head, &p->entry);
|
|
+ }
|
|
+ list_move_tail(&This->packet_free_head, &This->packet_filled_head);
|
|
+ }
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+
|
|
+ return hr;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_SetEventHandle(IAudioClient *iface,
|
|
+ HANDLE event)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ HRESULT hr;
|
|
+
|
|
+ TRACE("(%p)->(%p)\n", This, event);
|
|
+
|
|
+ if (!event)
|
|
+ return E_INVALIDARG;
|
|
+
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ hr = pulse_stream_valid(This);
|
|
+ if (FAILED(hr)) {
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return hr;
|
|
+ }
|
|
+
|
|
+ if (!(This->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK))
|
|
+ hr = AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED;
|
|
+ else if (This->event)
|
|
+ hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
|
|
+ else
|
|
+ This->event = event;
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ return hr;
|
|
+}
|
|
+
|
|
+static HRESULT WINAPI AudioClient_GetService(IAudioClient *iface, REFIID riid,
|
|
+ void **ppv)
|
|
+{
|
|
+ ACImpl *This = impl_from_IAudioClient(iface);
|
|
+ HRESULT hr;
|
|
+
|
|
+ TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv);
|
|
+
|
|
+ if (!ppv)
|
|
+ return E_POINTER;
|
|
+ *ppv = NULL;
|
|
+
|
|
+ pthread_mutex_lock(&pulse_lock);
|
|
+ hr = pulse_stream_valid(This);
|
|
+ pthread_mutex_unlock(&pulse_lock);
|
|
+ if (FAILED(hr))
|
|
+ return hr;
|
|
+
|
|
+ if (*ppv) {
|
|
+ IUnknown_AddRef((IUnknown*)*ppv);
|
|
+ return S_OK;
|
|
+ }
|
|
+
|
|
+ FIXME("stub %s\n", debugstr_guid(riid));
|
|
+ return E_NOINTERFACE;
|
|
+}
|
|
+
|
|
+static const IAudioClientVtbl AudioClient_Vtbl =
|
|
+{
|
|
+ AudioClient_QueryInterface,
|
|
+ AudioClient_AddRef,
|
|
+ AudioClient_Release,
|
|
+ AudioClient_Initialize,
|
|
+ AudioClient_GetBufferSize,
|
|
+ AudioClient_GetStreamLatency,
|
|
+ AudioClient_GetCurrentPadding,
|
|
+ AudioClient_IsFormatSupported,
|
|
+ AudioClient_GetMixFormat,
|
|
+ AudioClient_GetDevicePeriod,
|
|
+ AudioClient_Start,
|
|
+ AudioClient_Stop,
|
|
+ AudioClient_Reset,
|
|
+ AudioClient_SetEventHandle,
|
|
+ AudioClient_GetService
|
|
+};
|
|
+
|
|
HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device,
|
|
IAudioSessionManager2 **out)
|
|
{
|
|
--
|
|
1.8.4.4
|
|
|