diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 4c962069c97..d0d497c4bbb 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -127,13 +127,6 @@ typedef struct _AudioSessionWrapper { AudioSession *session; } AudioSessionWrapper; -typedef struct _ACPacket { - struct list entry; - UINT64 qpcpos; - BYTE *data; - UINT32 discont; -} ACPacket; - struct ACImpl { IAudioClient3 IAudioClient3_iface; IAudioRenderClient IAudioRenderClient_iface; @@ -221,34 +214,6 @@ static DWORD CALLBACK pulse_mainloop_thread(void *tmp) { return 0; } -static void pulse_stream_state(pa_stream *s, void *user) -{ - pa_stream_state_t state = pa_stream_get_state(s); - TRACE("Stream state changed to %i\n", state); - pulse->broadcast(); -} - -static const enum pa_channel_position pulse_pos_from_wfx[] = { - PA_CHANNEL_POSITION_FRONT_LEFT, - PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_FRONT_CENTER, - PA_CHANNEL_POSITION_LFE, - PA_CHANNEL_POSITION_REAR_LEFT, - PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, - PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, - PA_CHANNEL_POSITION_REAR_CENTER, - PA_CHANNEL_POSITION_SIDE_LEFT, - PA_CHANNEL_POSITION_SIDE_RIGHT, - PA_CHANNEL_POSITION_TOP_CENTER, - PA_CHANNEL_POSITION_TOP_FRONT_LEFT, - PA_CHANNEL_POSITION_TOP_FRONT_CENTER, - PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, - PA_CHANNEL_POSITION_TOP_REAR_LEFT, - PA_CHANNEL_POSITION_TOP_REAR_CENTER, - PA_CHANNEL_POSITION_TOP_REAR_RIGHT -}; - static char *get_application_name(void) { WCHAR path[MAX_PATH], *name; @@ -419,26 +384,12 @@ write: return pa_stream_write(This->pulse_stream->stream, buffer, bytes, NULL, 0, PA_SEEK_RELATIVE); } -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; pulse->broadcast(); } -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_write(ACImpl *This) { /* write as much data to PA as we can */ @@ -483,21 +434,6 @@ static void pulse_write(ACImpl *This) This->pulse_stream->pa_held_bytes -= to_write; } -static void pulse_underflow_callback(pa_stream *s, void *userdata) -{ - ACImpl *This = userdata; - WARN("%p: Underflow\n", userdata); - This->pulse_stream->just_underran = TRUE; - /* re-sync */ - This->pulse_stream->pa_offs_bytes = This->pulse_stream->lcl_offs_bytes; - This->pulse_stream->pa_held_bytes = This->pulse_stream->held_bytes; -} - -static void pulse_started_callback(pa_stream *s, void *userdata) -{ - TRACE("%p: (Re)started playing\n", userdata); -} - static void pulse_read(ACImpl *This) { size_t bytes = pa_stream_readable_size(This->pulse_stream->stream); @@ -680,58 +616,6 @@ static DWORD WINAPI pulse_timer_cb(void *user) return 0; } -static HRESULT pulse_stream_connect(ACImpl *This, pa_context *pulse_ctx, UINT32 period_bytes) { - int ret; - char buffer[64]; - static LONG number; - pa_buffer_attr attr; - if (This->pulse_stream->stream) { - pa_stream_disconnect(This->pulse_stream->stream); - while (pa_stream_get_state(This->pulse_stream->stream) == PA_STREAM_READY) - pulse->cond_wait(); - pa_stream_unref(This->pulse_stream->stream); - } - ret = InterlockedIncrement(&number); - sprintf(buffer, "audio stream #%i", ret); - This->pulse_stream->stream = pa_stream_new(pulse_ctx, buffer, &This->pulse_stream->ss, &This->pulse_stream->map); - - if (!This->pulse_stream->stream) { - WARN("pa_stream_new returned error %i\n", pa_context_errno(pulse_ctx)); - return AUDCLNT_E_ENDPOINT_CREATE_FAILED; - } - - pa_stream_set_state_callback(This->pulse_stream->stream, pulse_stream_state, This); - pa_stream_set_buffer_attr_callback(This->pulse_stream->stream, pulse_attr_update, This); - pa_stream_set_moved_callback(This->pulse_stream->stream, pulse_attr_update, This); - - /* PulseAudio will fill in correct values */ - attr.minreq = attr.fragsize = period_bytes; - attr.tlength = period_bytes * 3; - attr.maxlength = This->pulse_stream->bufsize_frames * pa_frame_size(&This->pulse_stream->ss); - attr.prebuf = pa_frame_size(&This->pulse_stream->ss); - dump_attr(&attr); - if (This->dataflow == eRender) - ret = pa_stream_connect_playback(This->pulse_stream->stream, NULL, &attr, - PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY, NULL, NULL); - else - ret = pa_stream_connect_record(This->pulse_stream->stream, NULL, &attr, - PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY); - if (ret < 0) { - WARN("Returns %i\n", ret); - return AUDCLNT_E_ENDPOINT_CREATE_FAILED; - } - while (pa_stream_get_state(This->pulse_stream->stream) == PA_STREAM_CREATING) - pulse->cond_wait(); - if (pa_stream_get_state(This->pulse_stream->stream) != PA_STREAM_READY) - return AUDCLNT_E_ENDPOINT_CREATE_FAILED; - - if (This->dataflow == eRender) { - pa_stream_set_underflow_callback(This->pulse_stream->stream, pulse_underflow_callback, This); - pa_stream_set_started_callback(This->pulse_stream->stream, pulse_started_callback, This); - } - return S_OK; -} - HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys, UINT *num, UINT *def_index) { @@ -936,32 +820,6 @@ static WAVEFORMATEX *clone_format(const WAVEFORMATEX *fmt) return ret; } -static DWORD get_channel_mask(unsigned int channels) -{ - switch(channels) { - case 0: - return 0; - case 1: - return KSAUDIO_SPEAKER_MONO; - case 2: - return KSAUDIO_SPEAKER_STEREO; - case 3: - return KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY; - case 4: - return KSAUDIO_SPEAKER_QUAD; /* not _SURROUND */ - case 5: - return KSAUDIO_SPEAKER_QUAD | SPEAKER_LOW_FREQUENCY; - case 6: - return KSAUDIO_SPEAKER_5POINT1; /* not 5POINT1_SURROUND */ - case 7: - return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER; - case 8: - return KSAUDIO_SPEAKER_7POINT1_SURROUND; /* Vista deprecates 7POINT1 */ - } - FIXME("Unknown speaker configuration: %u\n", channels); - return 0; -} - static void session_init_vols(AudioSession *session, UINT channels) { if (session->channel_count < channels) { @@ -1041,129 +899,21 @@ static HRESULT get_audio_session(const GUID *sessionguid, return S_OK; } -static HRESULT pulse_spec_from_waveformat(ACImpl *This, const WAVEFORMATEX *fmt) -{ - pa_channel_map_init(&This->pulse_stream->map); - This->pulse_stream->ss.rate = fmt->nSamplesPerSec; - This->pulse_stream->ss.format = PA_SAMPLE_INVALID; - - switch(fmt->wFormatTag) { - case WAVE_FORMAT_IEEE_FLOAT: - if (!fmt->nChannels || fmt->nChannels > 2 || fmt->wBitsPerSample != 32) - break; - This->pulse_stream->ss.format = PA_SAMPLE_FLOAT32LE; - pa_channel_map_init_auto(&This->pulse_stream->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); - break; - case WAVE_FORMAT_PCM: - if (!fmt->nChannels || fmt->nChannels > 2) - break; - if (fmt->wBitsPerSample == 8) - This->pulse_stream->ss.format = PA_SAMPLE_U8; - else if (fmt->wBitsPerSample == 16) - This->pulse_stream->ss.format = PA_SAMPLE_S16LE; - else - return AUDCLNT_E_UNSUPPORTED_FORMAT; - pa_channel_map_init_auto(&This->pulse_stream->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); - break; - case WAVE_FORMAT_EXTENSIBLE: { - WAVEFORMATEXTENSIBLE *wfe = (WAVEFORMATEXTENSIBLE*)fmt; - DWORD mask = wfe->dwChannelMask; - DWORD i = 0, j; - if (fmt->cbSize != (sizeof(*wfe) - sizeof(*fmt)) && fmt->cbSize != sizeof(*wfe)) - break; - if (IsEqualGUID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) && - (!wfe->Samples.wValidBitsPerSample || wfe->Samples.wValidBitsPerSample == 32) && - fmt->wBitsPerSample == 32) - This->pulse_stream->ss.format = PA_SAMPLE_FLOAT32LE; - else if (IsEqualGUID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) { - DWORD valid = wfe->Samples.wValidBitsPerSample; - if (!valid) - valid = fmt->wBitsPerSample; - if (!valid || valid > fmt->wBitsPerSample) - break; - switch (fmt->wBitsPerSample) { - case 8: - if (valid == 8) - This->pulse_stream->ss.format = PA_SAMPLE_U8; - break; - case 16: - if (valid == 16) - This->pulse_stream->ss.format = PA_SAMPLE_S16LE; - break; - case 24: - if (valid == 24) - This->pulse_stream->ss.format = PA_SAMPLE_S24LE; - break; - case 32: - if (valid == 24) - This->pulse_stream->ss.format = PA_SAMPLE_S24_32LE; - else if (valid == 32) - This->pulse_stream->ss.format = PA_SAMPLE_S32LE; - break; - default: - return AUDCLNT_E_UNSUPPORTED_FORMAT; - } - } - This->pulse_stream->map.channels = fmt->nChannels; - if (!mask || (mask & (SPEAKER_ALL|SPEAKER_RESERVED))) - mask = get_channel_mask(fmt->nChannels); - for (j = 0; j < ARRAY_SIZE(pulse_pos_from_wfx) && i < fmt->nChannels; ++j) { - if (mask & (1 << j)) - This->pulse_stream->map.map[i++] = pulse_pos_from_wfx[j]; - } - - /* Special case for mono since pulse appears to map it differently */ - if (mask == SPEAKER_FRONT_CENTER) - This->pulse_stream->map.map[0] = PA_CHANNEL_POSITION_MONO; - - if (i < fmt->nChannels || (mask & SPEAKER_RESERVED)) { - This->pulse_stream->map.channels = 0; - ERR("Invalid channel mask: %i/%i and %x(%x)\n", i, fmt->nChannels, mask, wfe->dwChannelMask); - break; - } - break; - } - case WAVE_FORMAT_ALAW: - case WAVE_FORMAT_MULAW: - if (fmt->wBitsPerSample != 8) { - FIXME("Unsupported bpp %u for LAW\n", fmt->wBitsPerSample); - return AUDCLNT_E_UNSUPPORTED_FORMAT; - } - if (fmt->nChannels != 1 && fmt->nChannels != 2) { - FIXME("Unsupported channels %u for LAW\n", fmt->nChannels); - return AUDCLNT_E_UNSUPPORTED_FORMAT; - } - This->pulse_stream->ss.format = fmt->wFormatTag == WAVE_FORMAT_MULAW ? PA_SAMPLE_ULAW : PA_SAMPLE_ALAW; - pa_channel_map_init_auto(&This->pulse_stream->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); - break; - default: - WARN("Unhandled tag %x\n", fmt->wFormatTag); - return AUDCLNT_E_UNSUPPORTED_FORMAT; - } - This->channel_count = This->pulse_stream->ss.channels = This->pulse_stream->map.channels; - if (!pa_channel_map_valid(&This->pulse_stream->map) || This->pulse_stream->ss.format == PA_SAMPLE_INVALID) { - ERR("Invalid format! Channel spec valid: %i, format: %i\n", pa_channel_map_valid(&This->pulse_stream->map), This->pulse_stream->ss.format); - return AUDCLNT_E_UNSUPPORTED_FORMAT; - } - return S_OK; -} - static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration, REFERENCE_TIME period, const WAVEFORMATEX *fmt, const GUID *sessionguid) { ACImpl *This = impl_from_IAudioClient3(iface); - pa_context *pulse_ctx; char *name; HRESULT hr = S_OK; - UINT32 bufsize_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; + dump_fmt(fmt); if (mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE) return E_INVALIDARG; @@ -1204,92 +954,19 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, } name = get_application_name(); - hr = pulse->connect(name, &pulse_ctx); + hr = pulse->create_stream(name, This->dataflow, mode, flags, duration, period, fmt, + &This->channel_count, &This->pulse_stream); free(name); - if (FAILED(hr)) { - pulse->unlock(); - return hr; - } - - if (!(This->pulse_stream = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This->pulse_stream)))) - return E_OUTOFMEMORY; - - This->pulse_stream->dataflow = This->dataflow; - - hr = pulse_spec_from_waveformat(This, fmt); - TRACE("Obtaining format returns %08x\n", hr); - dump_fmt(fmt); - - if (FAILED(hr)) - goto exit; - - period = pulse_config.modes[This->dataflow == eCapture].def_period; - if (duration < 3 * period) - duration = 3 * period; - - This->pulse_stream->period_bytes = pa_frame_size(&This->pulse_stream->ss) * MulDiv(period, This->pulse_stream->ss.rate, 10000000); - - This->pulse_stream->bufsize_frames = ceil((duration / 10000000.) * fmt->nSamplesPerSec); - bufsize_bytes = This->pulse_stream->bufsize_frames * pa_frame_size(&This->pulse_stream->ss); - This->pulse_stream->mmdev_period_usec = period / 10; - - This->pulse_stream->share = mode; - This->pulse_stream->flags = flags; - hr = pulse_stream_connect(This, pulse_ctx, This->pulse_stream->period_bytes); if (SUCCEEDED(hr)) { - UINT32 unalign; - const pa_buffer_attr *attr = pa_stream_get_buffer_attr(This->pulse_stream->stream); - This->pulse_stream->attr = *attr; - /* Update frames according to new size */ - dump_attr(attr); - if (This->dataflow == eRender) { - This->pulse_stream->real_bufsize_bytes = This->pulse_stream->bufsize_frames * 2 * pa_frame_size(&This->pulse_stream->ss); - This->pulse_stream->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->pulse_stream->real_bufsize_bytes); - if(!This->pulse_stream->local_buffer) - hr = E_OUTOFMEMORY; + hr = get_audio_session(sessionguid, This->parent, This->channel_count, &This->session); + if (SUCCEEDED(hr)) { + list_add_tail(&This->session->clients, &This->entry); } else { - UINT32 i, capture_packets; - - if ((unalign = bufsize_bytes % This->pulse_stream->period_bytes)) - bufsize_bytes += This->pulse_stream->period_bytes - unalign; - This->pulse_stream->bufsize_frames = bufsize_bytes / pa_frame_size(&This->pulse_stream->ss); - This->pulse_stream->real_bufsize_bytes = bufsize_bytes; - - capture_packets = This->pulse_stream->real_bufsize_bytes / This->pulse_stream->period_bytes; - - This->pulse_stream->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->pulse_stream->real_bufsize_bytes + capture_packets * sizeof(ACPacket)); - if (!This->pulse_stream->local_buffer) - hr = E_OUTOFMEMORY; - else { - ACPacket *cur_packet = (ACPacket*)((char*)This->pulse_stream->local_buffer + This->pulse_stream->real_bufsize_bytes); - BYTE *data = This->pulse_stream->local_buffer; - silence_buffer(This->pulse_stream->ss.format, This->pulse_stream->local_buffer, This->pulse_stream->real_bufsize_bytes); - list_init(&This->pulse_stream->packet_free_head); - list_init(&This->pulse_stream->packet_filled_head); - for (i = 0; i < capture_packets; ++i, ++cur_packet) { - list_add_tail(&This->pulse_stream->packet_free_head, &cur_packet->entry); - cur_packet->data = data; - data += This->pulse_stream->period_bytes; - } - } + pulse->release_stream(This->pulse_stream, NULL); + This->pulse_stream = NULL; } } - if (SUCCEEDED(hr)) - hr = get_audio_session(sessionguid, This->parent, fmt->nChannels, &This->session); - if (SUCCEEDED(hr)) - list_add_tail(&This->session->clients, &This->entry); -exit: - if (FAILED(hr)) { - HeapFree(GetProcessHeap(), 0, This->pulse_stream->local_buffer); - This->pulse_stream->local_buffer = NULL; - if (This->pulse_stream->stream) { - pa_stream_disconnect(This->pulse_stream->stream); - pa_stream_unref(This->pulse_stream->stream); - } - HeapFree(GetProcessHeap(), 0, This->pulse_stream); - This->pulse_stream = NULL; - } pulse->unlock(); return hr; } diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 23b362c8689..260723815f6 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -77,6 +78,39 @@ static void WINAPI pulse_broadcast(void) pthread_cond_broadcast(&pulse_cond); } +static void dump_attr(const pa_buffer_attr *attr) +{ + TRACE("maxlength: %u\n", attr->maxlength); + TRACE("minreq: %u\n", attr->minreq); + TRACE("fragsize: %u\n", attr->fragsize); + TRACE("tlength: %u\n", attr->tlength); + TRACE("prebuf: %u\n", attr->prebuf); +} + +/* copied from kernelbase */ +static int muldiv(int a, int b, int c) +{ + LONGLONG ret; + + if (!c) return -1; + + /* We want to deal with a positive divisor to simplify the logic. */ + if (c < 0) + { + a = -a; + c = -c; + } + + /* If the result is positive, we "add" to round. else, we subtract to round. */ + if ((a < 0 && b < 0) || (a >= 0 && b >= 0)) + ret = (((LONGLONG)a * b) + (c / 2)) / c; + else + ret = (((LONGLONG)a * b) - (c / 2)) / c; + + if (ret > 2147483647 || ret < -2147483647) return -1; + return ret; +} + /* 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 @@ -145,12 +179,36 @@ static void pulse_stream_state(pa_stream *s, void *user) pulse_broadcast(); } -static HRESULT WINAPI pulse_connect(const char *name, pa_context **ctx) +static void pulse_attr_update(pa_stream *s, void *user) { + const pa_buffer_attr *attr = pa_stream_get_buffer_attr(s); + TRACE("New attributes or device moved:\n"); + dump_attr(attr); +} + +static void pulse_underflow_callback(pa_stream *s, void *userdata) { - if (pulse_ctx && PA_CONTEXT_IS_GOOD(pa_context_get_state(pulse_ctx))) { - *ctx = pulse_ctx; + struct pulse_stream *stream = userdata; + WARN("%p: Underflow\n", userdata); + stream->just_underran = TRUE; + /* re-sync */ + stream->pa_offs_bytes = stream->lcl_offs_bytes; + stream->pa_held_bytes = stream->held_bytes; +} + +static void pulse_started_callback(pa_stream *s, void *userdata) +{ + TRACE("%p: (Re)started playing\n", userdata); +} + +static void silence_buffer(pa_sample_format_t format, BYTE *buffer, UINT32 bytes) +{ + memset(buffer, format == PA_SAMPLE_U8 ? 0x80 : 0, bytes); +} + +static HRESULT pulse_connect(const char *name) +{ + if (pulse_ctx && PA_CONTEXT_IS_GOOD(pa_context_get_state(pulse_ctx))) return S_OK; - } if (pulse_ctx) pa_context_unref(pulse_ctx); @@ -180,7 +238,6 @@ static HRESULT WINAPI pulse_connect(const char *name, pa_context **ctx) TRACE("Connected to server %s with protocol version: %i.\n", pa_context_get_server(pulse_ctx), pa_context_get_server_protocol_version(pulse_ctx)); - *ctx = pulse_ctx; return S_OK; fail: @@ -477,6 +534,300 @@ fail: return E_FAIL; } +static DWORD get_channel_mask(unsigned int channels) +{ + switch(channels) { + case 0: + return 0; + case 1: + return KSAUDIO_SPEAKER_MONO; + case 2: + return KSAUDIO_SPEAKER_STEREO; + case 3: + return KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY; + case 4: + return KSAUDIO_SPEAKER_QUAD; /* not _SURROUND */ + case 5: + return KSAUDIO_SPEAKER_QUAD | SPEAKER_LOW_FREQUENCY; + case 6: + return KSAUDIO_SPEAKER_5POINT1; /* not 5POINT1_SURROUND */ + case 7: + return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER; + case 8: + return KSAUDIO_SPEAKER_7POINT1_SURROUND; /* Vista deprecates 7POINT1 */ + } + FIXME("Unknown speaker configuration: %u\n", channels); + return 0; +} + +static const enum pa_channel_position pulse_pos_from_wfx[] = { + PA_CHANNEL_POSITION_FRONT_LEFT, + PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, + PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, + PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, + PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + PA_CHANNEL_POSITION_REAR_CENTER, + PA_CHANNEL_POSITION_SIDE_LEFT, + PA_CHANNEL_POSITION_SIDE_RIGHT, + PA_CHANNEL_POSITION_TOP_CENTER, + PA_CHANNEL_POSITION_TOP_FRONT_LEFT, + PA_CHANNEL_POSITION_TOP_FRONT_CENTER, + PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, + PA_CHANNEL_POSITION_TOP_REAR_LEFT, + PA_CHANNEL_POSITION_TOP_REAR_CENTER, + PA_CHANNEL_POSITION_TOP_REAR_RIGHT +}; + +static HRESULT pulse_spec_from_waveformat(struct pulse_stream *stream, const WAVEFORMATEX *fmt) +{ + pa_channel_map_init(&stream->map); + stream->ss.rate = fmt->nSamplesPerSec; + stream->ss.format = PA_SAMPLE_INVALID; + + switch(fmt->wFormatTag) { + case WAVE_FORMAT_IEEE_FLOAT: + if (!fmt->nChannels || fmt->nChannels > 2 || fmt->wBitsPerSample != 32) + break; + stream->ss.format = PA_SAMPLE_FLOAT32LE; + pa_channel_map_init_auto(&stream->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); + break; + case WAVE_FORMAT_PCM: + if (!fmt->nChannels || fmt->nChannels > 2) + break; + if (fmt->wBitsPerSample == 8) + stream->ss.format = PA_SAMPLE_U8; + else if (fmt->wBitsPerSample == 16) + stream->ss.format = PA_SAMPLE_S16LE; + else + return AUDCLNT_E_UNSUPPORTED_FORMAT; + pa_channel_map_init_auto(&stream->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); + break; + case WAVE_FORMAT_EXTENSIBLE: { + WAVEFORMATEXTENSIBLE *wfe = (WAVEFORMATEXTENSIBLE*)fmt; + DWORD mask = wfe->dwChannelMask; + DWORD i = 0, j; + if (fmt->cbSize != (sizeof(*wfe) - sizeof(*fmt)) && fmt->cbSize != sizeof(*wfe)) + break; + if (IsEqualGUID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) && + (!wfe->Samples.wValidBitsPerSample || wfe->Samples.wValidBitsPerSample == 32) && + fmt->wBitsPerSample == 32) + stream->ss.format = PA_SAMPLE_FLOAT32LE; + else if (IsEqualGUID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) { + DWORD valid = wfe->Samples.wValidBitsPerSample; + if (!valid) + valid = fmt->wBitsPerSample; + if (!valid || valid > fmt->wBitsPerSample) + break; + switch (fmt->wBitsPerSample) { + case 8: + if (valid == 8) + stream->ss.format = PA_SAMPLE_U8; + break; + case 16: + if (valid == 16) + stream->ss.format = PA_SAMPLE_S16LE; + break; + case 24: + if (valid == 24) + stream->ss.format = PA_SAMPLE_S24LE; + break; + case 32: + if (valid == 24) + stream->ss.format = PA_SAMPLE_S24_32LE; + else if (valid == 32) + stream->ss.format = PA_SAMPLE_S32LE; + break; + default: + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + } + stream->map.channels = fmt->nChannels; + if (!mask || (mask & (SPEAKER_ALL|SPEAKER_RESERVED))) + mask = get_channel_mask(fmt->nChannels); + for (j = 0; j < ARRAY_SIZE(pulse_pos_from_wfx) && i < fmt->nChannels; ++j) { + if (mask & (1 << j)) + stream->map.map[i++] = pulse_pos_from_wfx[j]; + } + + /* Special case for mono since pulse appears to map it differently */ + if (mask == SPEAKER_FRONT_CENTER) + stream->map.map[0] = PA_CHANNEL_POSITION_MONO; + + if (i < fmt->nChannels || (mask & SPEAKER_RESERVED)) { + stream->map.channels = 0; + ERR("Invalid channel mask: %i/%i and %x(%x)\n", i, fmt->nChannels, mask, wfe->dwChannelMask); + break; + } + break; + } + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + if (fmt->wBitsPerSample != 8) { + FIXME("Unsupported bpp %u for LAW\n", fmt->wBitsPerSample); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + if (fmt->nChannels != 1 && fmt->nChannels != 2) { + FIXME("Unsupported channels %u for LAW\n", fmt->nChannels); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + stream->ss.format = fmt->wFormatTag == WAVE_FORMAT_MULAW ? PA_SAMPLE_ULAW : PA_SAMPLE_ALAW; + pa_channel_map_init_auto(&stream->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); + break; + default: + WARN("Unhandled tag %x\n", fmt->wFormatTag); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + stream->ss.channels = stream->map.channels; + if (!pa_channel_map_valid(&stream->map) || stream->ss.format == PA_SAMPLE_INVALID) { + ERR("Invalid format! Channel spec valid: %i, format: %i\n", + pa_channel_map_valid(&stream->map), stream->ss.format); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + return S_OK; +} + +static HRESULT pulse_stream_connect(struct pulse_stream *stream, UINT32 period_bytes) +{ + int ret; + char buffer[64]; + static LONG number; + pa_buffer_attr attr; + + ret = InterlockedIncrement(&number); + sprintf(buffer, "audio stream #%i", ret); + stream->stream = pa_stream_new(pulse_ctx, buffer, &stream->ss, &stream->map); + + if (!stream->stream) { + WARN("pa_stream_new returned error %i\n", pa_context_errno(pulse_ctx)); + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; + } + + pa_stream_set_state_callback(stream->stream, pulse_stream_state, stream); + pa_stream_set_buffer_attr_callback(stream->stream, pulse_attr_update, stream); + pa_stream_set_moved_callback(stream->stream, pulse_attr_update, stream); + + /* PulseAudio will fill in correct values */ + attr.minreq = attr.fragsize = period_bytes; + attr.tlength = period_bytes * 3; + attr.maxlength = stream->bufsize_frames * pa_frame_size(&stream->ss); + attr.prebuf = pa_frame_size(&stream->ss); + dump_attr(&attr); + if (stream->dataflow == eRender) + ret = pa_stream_connect_playback(stream->stream, NULL, &attr, + PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY, NULL, NULL); + else + ret = pa_stream_connect_record(stream->stream, NULL, &attr, + PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY); + if (ret < 0) { + WARN("Returns %i\n", ret); + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; + } + while (pa_stream_get_state(stream->stream) == PA_STREAM_CREATING) + pulse_cond_wait(); + if (pa_stream_get_state(stream->stream) != PA_STREAM_READY) + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; + + if (stream->dataflow == eRender) { + pa_stream_set_underflow_callback(stream->stream, pulse_underflow_callback, stream); + pa_stream_set_started_callback(stream->stream, pulse_started_callback, stream); + } + return S_OK; +} + +static HRESULT WINAPI pulse_create_stream(const char *name, EDataFlow dataflow, AUDCLNT_SHAREMODE mode, + DWORD flags, REFERENCE_TIME duration, REFERENCE_TIME period, + const WAVEFORMATEX *fmt, UINT32 *channel_count, + struct pulse_stream **ret) +{ + struct pulse_stream *stream; + unsigned int bufsize_bytes; + HRESULT hr; + + if (FAILED(hr = pulse_connect(name))) + return hr; + + if (!(stream = RtlAllocateHeap(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*stream)))) + return E_OUTOFMEMORY; + + stream->dataflow = dataflow; + + hr = pulse_spec_from_waveformat(stream, fmt); + TRACE("Obtaining format returns %08x\n", hr); + + if (FAILED(hr)) + goto exit; + + period = pulse_def_period[dataflow == eCapture]; + if (duration < 3 * period) + duration = 3 * period; + + stream->period_bytes = pa_frame_size(&stream->ss) * muldiv(period, stream->ss.rate, 10000000); + + stream->bufsize_frames = ceil((duration / 10000000.) * fmt->nSamplesPerSec); + bufsize_bytes = stream->bufsize_frames * pa_frame_size(&stream->ss); + stream->mmdev_period_usec = period / 10; + + stream->share = mode; + stream->flags = flags; + hr = pulse_stream_connect(stream, stream->period_bytes); + if (SUCCEEDED(hr)) { + UINT32 unalign; + const pa_buffer_attr *attr = pa_stream_get_buffer_attr(stream->stream); + stream->attr = *attr; + /* Update frames according to new size */ + dump_attr(attr); + if (dataflow == eRender) { + stream->real_bufsize_bytes = stream->bufsize_frames * 2 * pa_frame_size(&stream->ss); + stream->local_buffer = RtlAllocateHeap(GetProcessHeap(), 0, stream->real_bufsize_bytes); + if(!stream->local_buffer) + hr = E_OUTOFMEMORY; + } else { + UINT32 i, capture_packets; + + if ((unalign = bufsize_bytes % stream->period_bytes)) + bufsize_bytes += stream->period_bytes - unalign; + stream->bufsize_frames = bufsize_bytes / pa_frame_size(&stream->ss); + stream->real_bufsize_bytes = bufsize_bytes; + + capture_packets = stream->real_bufsize_bytes / stream->period_bytes; + + stream->local_buffer = RtlAllocateHeap(GetProcessHeap(), 0, stream->real_bufsize_bytes + capture_packets * sizeof(ACPacket)); + if (!stream->local_buffer) + hr = E_OUTOFMEMORY; + else { + ACPacket *cur_packet = (ACPacket*)((char*)stream->local_buffer + stream->real_bufsize_bytes); + BYTE *data = stream->local_buffer; + silence_buffer(stream->ss.format, stream->local_buffer, stream->real_bufsize_bytes); + list_init(&stream->packet_free_head); + list_init(&stream->packet_filled_head); + for (i = 0; i < capture_packets; ++i, ++cur_packet) { + list_add_tail(&stream->packet_free_head, &cur_packet->entry); + cur_packet->data = data; + data += stream->period_bytes; + } + } + } + } + +exit: + if (FAILED(hr)) { + free(stream->local_buffer); + if (stream->stream) { + pa_stream_disconnect(stream->stream); + pa_stream_unref(stream->stream); + RtlFreeHeap(GetProcessHeap(), 0, stream); + } + return hr; + } + + *channel_count = stream->ss.channels; + *ret = stream; + return S_OK; +} + static void WINAPI pulse_release_stream(struct pulse_stream *stream, HANDLE timer) { if(timer) { @@ -507,7 +858,7 @@ static const struct unix_funcs unix_funcs = pulse_cond_wait, pulse_broadcast, pulse_main_loop, - pulse_connect, + pulse_create_stream, pulse_release_stream, pulse_test_connect, }; diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index d02ab4fdf07..51c850ac1ae 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -29,6 +29,14 @@ struct pulse_config unsigned int speakers_mask; }; +typedef struct _ACPacket +{ + struct list entry; + UINT64 qpcpos; + BYTE *data; + UINT32 discont; +} ACPacket; + struct pulse_stream { EDataFlow dataflow; @@ -64,7 +72,10 @@ struct unix_funcs int (WINAPI *cond_wait)(void); void (WINAPI *broadcast)(void); void (WINAPI *main_loop)(void); - HRESULT (WINAPI *connect)(const char *name, pa_context **ret); + HRESULT (WINAPI *create_stream)(const char *name, EDataFlow dataflow, AUDCLNT_SHAREMODE mode, + DWORD flags, REFERENCE_TIME duration, REFERENCE_TIME period, + const WAVEFORMATEX *fmt, UINT32 *channel_count, + struct pulse_stream **ret); void (WINAPI *release_stream)(struct pulse_stream *stream, HANDLE timer); HRESULT (WINAPI *test_connect)(const char *name, struct pulse_config *config); };