From 9f5487715a35d03adcf34303695f4e56e61bb56a Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Thu, 26 Jul 2012 08:34:55 +0200 Subject: [PATCH] winepulse: Add audioclient --- 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 40db26d8344..37d85ff977f 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) { -- 2.11.4.GIT