push 5f793418e14616d83a4fb368b755a8821b49820b
[wine/hacks.git] / dlls / winepulse.drv / dsoutput.c
blob203fac00ef09c99ae4f49e166d6741c8353d96bc
1 /*
2 * Wine Driver for PulseAudio - DSound Output Functionality
3 * http://pulseaudio.org/
5 * Copyright 2009 Arthur Talyor <theycallhimart@gmail.com>
7 * Conatins code from other wine multimedia drivers.
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 #include "config.h"
26 #include <stdarg.h>
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "winuser.h"
32 #include "winerror.h"
33 #include "mmddk.h"
34 #include "dsound.h"
35 #include "dsdriver.h"
36 #include "winreg.h"
38 #include <winepulse.h>
39 #include "wine/debug.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(dspulse);
43 #if HAVE_PULSEAUDIO
45 /*======================================================================*
46 * Low level DSOUND implementation *
47 *======================================================================*/
49 /* A buffer is allocated with a pointer indicating the read position in the
50 * buffer. The pulse write callback reads data from the buffer, updating the
51 * read pointer to the location at the end of the read. Upon reaching the end
52 * or attempting to read past the end of buffer the read pointer wraps around
53 * to the beginning again. DirectSound applications can write anywhere in the
54 * buffer at anytime without locking and can know the location of the read
55 * pointer. The position of the read pointer cannot be changed by the
56 * application and access to it uses a locking scheme. A fake pointer
57 * indicating estimated playback position is also available to the application.
58 * Applications can potentially write to the same area of memory which is also
59 * being read by the pulse thread. However, this is uncommon as directsound
60 * applications know where pulse should be reading from via the pointer
61 * locations and MSDN says that such an operation should be avoided with the
62 * results being undefined.
65 /* Fragment lengths try to be a power of two close to 10ms worth of data. See
66 * dlls/dsound/mixer.c
68 static int fragment_length(pa_sample_spec *s) {
69 if (s->rate <= 12800)
70 return 128 * pa_frame_size(s);
72 if (s->rate <= 25600)
73 return 256 * pa_frame_size(s);
75 if (s->rate <= 51200)
76 return 512 * pa_frame_size(s);
78 return 1024 * pa_frame_size(s);
81 /* Callback from the pulse thread for stream data */
82 static void DSPULSE_BufferReadCallback(pa_stream *s, size_t nbytes, void *userdata) {
83 IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)userdata;
85 if (!This->buffer)
86 return;
88 /* Fraglens are always powers of 2 */
89 nbytes+= This->fraglen - 1;
90 nbytes&= ~(This->fraglen - 1);
92 /* If we advance more than 10 fragments at a time it appears that the buffer
93 * pointer is never advancing because of wrap-around. Evil magic numbers. */
94 if (nbytes > This->fraglen * 5)
95 nbytes = This->fraglen * 5;
97 TRACE("Reading %u bytes.\n", nbytes);
99 if (This->buffer_read_offset + nbytes <= This->buffer_length) {
100 pa_stream_write(s, This->buffer + This->buffer_read_offset, nbytes, NULL, 0, PA_SEEK_RELATIVE);
101 This->buffer_play_offset = This->buffer_read_offset;
102 This->buffer_read_offset += nbytes;
103 } else {
104 size_t write_length = This->buffer_length - This->buffer_read_offset;
105 nbytes -= write_length;
106 pa_stream_write(s, This->buffer + This->buffer_read_offset, write_length, NULL, 0, PA_SEEK_RELATIVE);
107 pa_stream_write(s, This->buffer, nbytes, NULL, 0, PA_SEEK_RELATIVE);
108 This->buffer_play_offset = This->buffer_read_offset;
109 This->buffer_read_offset = nbytes;
112 This->buffer_read_offset %= This->buffer_length;
115 /* Called when the stream underruns. Just for information */
116 static void DSPULSE_BufferUnderflowCallback(pa_stream *s, void *userdata) {
117 WARN("(%p) underrun.\n", userdata);
120 /* Connects a stream to the server. Does not update
121 * IDsDriverBufferImpl->fraglen. Does not lock the pulse mainloop or free
122 * objects in case of failure. This should be handled by the calling function.
124 static HRESULT DSPULSE_ConnectStream(IDsDriverBufferImpl* This) {
125 pa_buffer_attr ba_request;
126 const pa_buffer_attr *ba_obtained;
127 char c[PA_SAMPLE_SPEC_SNPRINT_MAX];
128 pa_stream_flags_t stream_flags = PA_STREAM_START_CORKED;
130 #if PA_PROTOCOL_VERSION >= 14
131 /* We are a "fragment wait based" application, so this flag should be
132 * acceptable. */
133 stream_flags |= PA_STREAM_EARLY_REQUESTS;
134 #elif PA_PROTOCOL_VERSION >= 13 && PA_API_VERSION >= 12
135 stream_flags |= PA_STREAM_ADJUST_LATENCY;
136 #endif
138 pa_sample_spec_snprint(c, PA_SAMPLE_SPEC_SNPRINT_MAX, &This->sample_spec);
139 TRACE("Sample spec %s fragment size %u.\n", c, This->fraglen);
141 ba_request.tlength = This->fraglen * 4; // ~40ms
142 ba_request.minreq = This->fraglen; // ~10ms
143 ba_request.prebuf = (uint32_t)-1; // same as tlength
144 ba_request.maxlength = This->buffer_length; // 2^x = ~3s
146 TRACE("Asking for buffer tlength:%u (%llums) minreq:%u (%llums)\n",
147 ba_request.tlength, pa_bytes_to_usec(ba_request.tlength, &This->sample_spec)/1000,
148 ba_request.minreq, pa_bytes_to_usec(ba_request.minreq, &This->sample_spec)/1000);
150 This->stream = pa_stream_new(PULSE_context, "DirectSound Buffer", &This->sample_spec, NULL);
151 if (!This->stream) return DSERR_BADFORMAT;
153 pa_stream_set_state_callback(This->stream, PULSE_StreamStateCallback, This);
154 pa_stream_set_write_callback(This->stream, DSPULSE_BufferReadCallback, This);
155 pa_stream_set_underflow_callback(This->stream, DSPULSE_BufferUnderflowCallback, This);
157 TRACE("Attempting to connect (%p)->stream for playback on %s\n", This, WOutDev[This->drv->wDevID].device_name);
158 pa_stream_connect_playback(This->stream, WOutDev[This->drv->wDevID].device_name, &ba_request, stream_flags, NULL, NULL);
159 for (;;) {
160 pa_context_state_t cstate = pa_context_get_state(PULSE_context);
161 pa_stream_state_t sstate = pa_stream_get_state(This->stream);
163 if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED ||
164 sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) {
165 ERR("Failed to connect stream context object: %s\n", pa_strerror(pa_context_errno(PULSE_context)));
166 return DSERR_BUFFERLOST;
169 if (sstate == PA_STREAM_READY)
170 break;
172 pa_threaded_mainloop_wait(PULSE_ml);
174 TRACE("(%p)->stream connected for playback.\n", This);
175 ba_obtained = pa_stream_get_buffer_attr(This->stream);
177 TRACE("Obtained buffer tlength:%u (%llums) minreq:%u (%llums)\n",
178 ba_obtained->tlength, pa_bytes_to_usec(ba_obtained->tlength, &This->sample_spec)/1000,
179 ba_obtained->minreq, pa_bytes_to_usec(ba_obtained->minreq, &This->sample_spec)/1000);
181 return DS_OK;
184 static HRESULT WINAPI IDsDriverBufferImpl_QueryInterface(PIDSDRIVERBUFFER iface, REFIID riid, LPVOID *ppobj) {
185 /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
186 FIXME("(): stub!\n");
187 return DSERR_UNSUPPORTED;
190 static ULONG WINAPI IDsDriverBufferImpl_AddRef(PIDSDRIVERBUFFER iface) {
191 IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
192 ULONG refCount = InterlockedIncrement(&This->ref);
194 TRACE("(%p)->(ref before=%u)\n",This, refCount - 1);
196 return refCount;
199 static ULONG WINAPI IDsDriverBufferImpl_Release(PIDSDRIVERBUFFER iface) {
200 IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
201 ULONG refCount = InterlockedDecrement(&This->ref);
203 TRACE("(%p)->(ref before=%u)\n",This, refCount + 1);
205 if (refCount)
206 return refCount;
208 TRACE("mmap buffer %p destroyed\n", This->buffer);
209 pa_threaded_mainloop_lock(PULSE_ml);
210 PULSE_WaitForOperation(pa_stream_cork(This->stream, 1, PULSE_StreamSuccessCallback, This));
211 pa_stream_disconnect(This->stream);
212 if (This == This->drv->primary)
213 This->drv->primary = NULL;
214 HeapFree(GetProcessHeap(), 0, This->buffer);
215 This->buffer = NULL;
216 pa_stream_unref(This->stream);
217 This->stream = NULL;
218 HeapFree(GetProcessHeap(), 0, This);
219 pa_threaded_mainloop_unlock(PULSE_ml);
221 return 0;
224 static HRESULT WINAPI IDsDriverBufferImpl_Lock(PIDSDRIVERBUFFER iface,
225 LPVOID*ppvAudio1,LPDWORD pdwLen1,
226 LPVOID*ppvAudio2,LPDWORD pdwLen2,
227 DWORD dwWritePosition,DWORD dwWriteLen,
228 DWORD dwFlags)
230 /* We set DSDDESC_DONTNEEDPRIMARYLOCK so this should never be called */
231 TRACE("(%p): stub", iface);
232 return DSERR_UNSUPPORTED;
235 static HRESULT WINAPI IDsDriverBufferImpl_Unlock(PIDSDRIVERBUFFER iface,
236 LPVOID pvAudio1,DWORD dwLen1,
237 LPVOID pvAudio2,DWORD dwLen2)
239 /* We set DSDDESC_DONTNEEDPRIMARYLOCK so this should never be called */
240 TRACE("(%p): stub", iface);
241 return DSERR_UNSUPPORTED;
244 /* You cannot change the sample format of a connected stream, so we need to
245 * destroy and re-create the stream if the sample spec is different */
246 static HRESULT WINAPI IDsDriverBufferImpl_SetFormat(PIDSDRIVERBUFFER iface, LPWAVEFORMATEX pwfx) {
247 IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
248 pa_sample_spec old_spec;
250 TRACE("(%p, %p)\n", iface, pwfx);
252 old_spec.rate = This->sample_spec.rate;
253 old_spec.format = This->sample_spec.format;
254 old_spec.channels = This->sample_spec.channels;
256 if (!PULSE_SetupFormat(pwfx, &This->sample_spec))
257 return DSERR_BADFORMAT;
259 if (old_spec.rate == This->sample_spec.rate &&
260 old_spec.format == This->sample_spec.format &&
261 old_spec.channels == This->sample_spec.channels) {
262 TRACE("same as original sample spec, exiting.\n");
263 PULSE_WaitForOperation(pa_stream_flush(This->stream, PULSE_StreamSuccessCallback, This));
264 return DS_OK;
267 /* If the format doesn't match, return an error and the buffer will be remade */
268 TRACE("Formats don't match, failing causing re-creation.\n");
269 return DSERR_BUFFERLOST;
272 static HRESULT WINAPI IDsDriverBufferImpl_SetFrequency(PIDSDRIVERBUFFER iface, DWORD dwFreq)
274 /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
275 /* You can't do for primary buffers anyways */
276 return S_OK;
279 static HRESULT WINAPI IDsDriverBufferImpl_SetVolumePan(PIDSDRIVERBUFFER iface, PDSVOLUMEPAN pVolPan) {
280 IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
282 if (This->sample_spec.channels == 2) {
283 This->volume.channels = 2;
284 if (pVolPan->lPan < 0) {
285 This->volume.values[0] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0 + pVolPan->lPan/10.0);
286 This->volume.values[1] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0);
287 } else {
288 This->volume.values[0] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0);
289 This->volume.values[1] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0 - pVolPan->lPan/10.0);
291 } else {
292 WARN("Panning non-stereo streams not supported yet!\n");
293 pa_cvolume_set(&This->volume, This->sample_spec.channels, pa_sw_volume_from_dB(pVolPan->lVolume/-10.0));
294 /* Would be nice to return DSERR_CONTROLUNAVAIL, but that isn't up to us
295 * here */
298 pa_threaded_mainloop_lock(PULSE_ml);
299 if (This->stream && PULSE_context && pa_context_get_state(PULSE_context) == PA_CONTEXT_READY &&
300 pa_stream_get_state(This->stream) == PA_STREAM_READY && pa_cvolume_valid(&This->volume))
301 PULSE_WaitForOperation(
302 pa_context_set_sink_input_volume(
303 PULSE_context, pa_stream_get_index(This->stream),
304 &This->volume, PULSE_ContextSuccessCallback, This));
306 pa_threaded_mainloop_unlock(PULSE_ml);
308 return DS_OK;
311 static HRESULT WINAPI IDsDriverBufferImpl_SetPosition(PIDSDRIVERBUFFER iface, DWORD dwNewPos)
313 /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
314 /* Not allowed on primary buffers */
315 return DSERR_UNSUPPORTED;
318 static HRESULT WINAPI IDsDriverBufferImpl_GetPosition(PIDSDRIVERBUFFER iface,
319 LPDWORD lpdwPlay, LPDWORD lpdwWrite)
321 IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
323 pa_threaded_mainloop_lock(PULSE_ml);
324 if (!This->buffer || pa_stream_get_state(This->stream) != PA_STREAM_READY) {
325 pa_threaded_mainloop_unlock(PULSE_ml);
326 return DSERR_UNINITIALIZED;
329 if (lpdwPlay)
330 *lpdwPlay = This->buffer_play_offset;
331 if (lpdwWrite)
332 *lpdwWrite = This->buffer_read_offset;
333 pa_threaded_mainloop_unlock(PULSE_ml);
335 return DS_OK;
338 static HRESULT WINAPI IDsDriverBufferImpl_Play(PIDSDRIVERBUFFER iface, DWORD dwRes1, DWORD dwRes2, DWORD dwFlags) {
339 IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
340 TRACE("(%p,%x,%x,%x)\n",iface,dwRes1,dwRes2,dwFlags);
342 pa_threaded_mainloop_lock(PULSE_ml);
343 PULSE_WaitForOperation(pa_stream_cork(This->stream, 0, PULSE_StreamSuccessCallback, This));
344 pa_threaded_mainloop_unlock(PULSE_ml);
346 return DS_OK;
349 static HRESULT WINAPI IDsDriverBufferImpl_Stop(PIDSDRIVERBUFFER iface) {
350 IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
351 TRACE("(%p)\n",iface);
353 pa_threaded_mainloop_lock(PULSE_ml);
354 PULSE_WaitForOperation(pa_stream_cork(This->stream, 1, PULSE_StreamSuccessCallback, This));
355 pa_threaded_mainloop_unlock(PULSE_ml);
356 return DS_OK;
359 /*****************************************************************************/
361 static const IDsDriverBufferVtbl dsdbvt =
363 IDsDriverBufferImpl_QueryInterface,
364 IDsDriverBufferImpl_AddRef,
365 IDsDriverBufferImpl_Release,
366 IDsDriverBufferImpl_Lock,
367 IDsDriverBufferImpl_Unlock,
368 IDsDriverBufferImpl_SetFormat,
369 IDsDriverBufferImpl_SetFrequency,
370 IDsDriverBufferImpl_SetVolumePan,
371 IDsDriverBufferImpl_SetPosition,
372 IDsDriverBufferImpl_GetPosition,
373 IDsDriverBufferImpl_Play,
374 IDsDriverBufferImpl_Stop
377 static HRESULT WINAPI IDsDriverImpl_CreateSoundBuffer(PIDSDRIVER iface,
378 LPWAVEFORMATEX pwfx,
379 DWORD dwFlags, DWORD dwCardAddress,
380 LPDWORD pdwcbBufferSize,
381 LPBYTE *ppbBuffer,
382 LPVOID *ppvObj) {
383 IDsDriverImpl *This = (IDsDriverImpl *)iface;
384 IDsDriverBufferImpl** ippdsdb = (IDsDriverBufferImpl**)ppvObj;
385 IDsDriverBufferImpl *That;
386 HRESULT ret;
388 TRACE("(%p,%p,%x,%x)\n",iface,pwfx,dwFlags,dwCardAddress);
389 /* we only support primary buffers */
391 if (!(dwFlags & DSBCAPS_PRIMARYBUFFER))
392 return DSERR_UNSUPPORTED;
393 if (This->primary)
394 return DSERR_ALLOCATED;
395 This->primary = *ippdsdb;
397 *ippdsdb = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDsDriverBufferImpl));
399 if (!*ippdsdb) {
400 ERR("Out of memory\n");
401 return DSERR_OUTOFMEMORY;
404 That = *ippdsdb;
405 That->lpVtbl = &dsdbvt;
406 That->ref = 1;
408 That->drv = This;
409 TRACE("IdsDriverBufferImpl %p created.\n", That);
411 if (!PULSE_SetupFormat(pwfx, &That->sample_spec)) {
412 WARN("Bad audio format.\n");
413 ret = DSERR_BADFORMAT;
414 goto err;
417 /* The buffer length has to be greater than fraglen * 20 or else logic in
418 * dlls/dsound/mixer.c fails to correctly understand buffer wrap around. */
419 That->fraglen = fragment_length(&That->sample_spec);
420 That->buffer_length = That->fraglen * 32;
421 That->buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, That->buffer_length);
422 if (!That->buffer) {
423 ret = DSERR_OUTOFMEMORY;
424 goto err;
426 That->buffer_read_offset = 0;
427 That->buffer_play_offset = 0;
429 pa_threaded_mainloop_lock(PULSE_ml);
430 ret = DSPULSE_ConnectStream(That);
431 pa_threaded_mainloop_unlock(PULSE_ml);
433 if (ret != DS_OK)
434 goto err;
436 *pdwcbBufferSize = That->buffer_length;
437 *ppbBuffer = That->buffer;
439 TRACE("Exiting with success. Have buffer %p of length %u\n", That->buffer, That->buffer_length);
440 return DS_OK;
442 err:
443 pa_threaded_mainloop_lock(PULSE_ml);
444 if (That->stream) {
445 if (pa_stream_get_state(That->stream) == PA_STREAM_READY)
446 pa_stream_disconnect(That->stream);
447 pa_stream_unref(That->stream);
448 That->stream = NULL;
450 pa_threaded_mainloop_unlock(PULSE_ml);
451 HeapFree(GetProcessHeap(), 0, That->buffer);
452 HeapFree(GetProcessHeap(), 0, *ippdsdb);
453 WARN("exiting with failure.\n");
454 return ret;
457 static HRESULT WINAPI IDsDriverImpl_QueryInterface(PIDSDRIVER iface, REFIID riid, LPVOID *ppobj) {
458 /* IDsDriverImpl *This = (IDsDriverImpl *)iface; */
459 FIXME("(%p): stub!\n",iface);
460 return DSERR_UNSUPPORTED;
463 static ULONG WINAPI IDsDriverImpl_AddRef(PIDSDRIVER iface) {
464 IDsDriverImpl *This = (IDsDriverImpl *)iface;
465 ULONG refCount = InterlockedIncrement(&This->ref);
467 TRACE("(%p)->(ref before=%u)\n",This, refCount - 1);
469 return refCount;
472 static ULONG WINAPI IDsDriverImpl_Release(PIDSDRIVER iface) {
473 IDsDriverImpl *This = (IDsDriverImpl *)iface;
474 ULONG refCount = InterlockedDecrement(&This->ref);
476 TRACE("(%p)->(ref before=%u)\n",This, refCount + 1);
478 if (refCount)
479 return refCount;
481 HeapFree(GetProcessHeap(), 0, This);
482 return 0;
485 static HRESULT WINAPI IDsDriverImpl_GetDriverDesc(PIDSDRIVER iface, PDSDRIVERDESC pDesc) {
486 IDsDriverImpl *This = (IDsDriverImpl *)iface;
487 TRACE("(%p,%p)\n",iface,pDesc);
488 *pDesc = WOutDev[This->wDevID].ds_desc;
489 pDesc->dwFlags = DSDDESC_DONTNEEDSECONDARYLOCK | DSDDESC_DONTNEEDPRIMARYLOCK;
490 pDesc->dnDevNode = 0; /*TODO: Bwah? */
491 pDesc->wVxdId = 0;
492 pDesc->wReserved = 0;
493 pDesc->ulDeviceNum = This->wDevID;
494 pDesc->dwHeapType = DSDHEAP_NOHEAP;
495 pDesc->pvDirectDrawHeap = NULL;
496 pDesc->dwMemStartAddress = 0xDEAD0000;
497 pDesc->dwMemEndAddress = 0xDEAF0000;
498 pDesc->dwMemAllocExtra = 0;
499 pDesc->pvReserved1 = NULL;
500 pDesc->pvReserved2 = NULL;
501 return DS_OK;
504 static HRESULT WINAPI IDsDriverImpl_Close(PIDSDRIVER iface) {
505 IDsDriverImpl *This = (IDsDriverImpl *)iface;
506 TRACE("(%p) stub, harmless\n",This);
507 return DS_OK;
510 static HRESULT WINAPI IDsDriverImpl_Open(PIDSDRIVER iface) {
511 IDsDriverImpl *This = (IDsDriverImpl *)iface;
512 TRACE("(%p) stub, harmless\n",This);
513 return S_OK;
516 static HRESULT WINAPI IDsDriverImpl_GetCaps(PIDSDRIVER iface, PDSDRIVERCAPS pCaps) {
517 IDsDriverImpl *This = (IDsDriverImpl *)iface;
519 if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY) {
520 ERR("Context failure.\n");
521 return DSERR_GENERIC;
524 TRACE("(%p,%p)\n",iface,pCaps);
525 *pCaps = WOutDev[This->wDevID].ds_caps;
526 return DS_OK;
529 static HRESULT WINAPI IDsDriverImpl_DuplicateSoundBuffer(PIDSDRIVER iface,
530 PIDSDRIVERBUFFER pBuffer,
531 LPVOID *ppvObj)
533 IDsDriverImpl *This = (IDsDriverImpl *)iface;
534 FIXME("(%p,%p): stub\n",This,pBuffer);
535 return DSERR_INVALIDCALL;
538 static const IDsDriverVtbl dsdvt =
540 IDsDriverImpl_QueryInterface,
541 IDsDriverImpl_AddRef,
542 IDsDriverImpl_Release,
543 IDsDriverImpl_GetDriverDesc,
544 IDsDriverImpl_Open,
545 IDsDriverImpl_Close,
546 IDsDriverImpl_GetCaps,
547 IDsDriverImpl_CreateSoundBuffer,
548 IDsDriverImpl_DuplicateSoundBuffer
551 DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv) {
552 IDsDriverImpl** idrv = (IDsDriverImpl**)drv;
554 if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || pa_context_is_local(PULSE_context) != 1) {
555 WARN("Connection failure or server is not local, falling back to WaveOut HEL.\n");
556 return MMSYSERR_NOTSUPPORTED;
559 *idrv = HeapAlloc(GetProcessHeap(), 0, sizeof(IDsDriverImpl));
560 if (!*idrv)
561 return MMSYSERR_NOMEM;
563 TRACE("IDsDriverImpl %p created.\n", *idrv);
565 (*idrv)->lpVtbl = &dsdvt;
566 (*idrv)->ref = 1;
567 (*idrv)->wDevID = wDevID;
568 (*idrv)->primary = NULL;
570 return MMSYSERR_NOERROR;
573 DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc) {
574 TRACE("(%u, %p)\n", wDevID, desc);
575 *desc = WOutDev[wDevID].ds_desc;
576 return MMSYSERR_NOERROR;
578 #endif /* HAVE_PULSEAUDIO */