wine-staging/patches/06-winepulse/0004-winepulse-Add-audioclient.patch

1099 lines
35 KiB
Diff
Raw Normal View History

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