| /* | |
| * Sample Wine Driver for Open Sound System (featured in Linux and FreeBSD) | |
| * | |
| * Copyright 1994 Martin Ayotte | |
| * 1999 Eric Pouech (async playing in waveOut/waveIn) | |
| * 2000 Eric Pouech (loops in waveOut) | |
| * 2002 Eric Pouech (full duplex) | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| */ | |
| #include "config.h" | |
| #include "wine/port.h" | |
| #include <stdlib.h> | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #ifdef HAVE_UNISTD_H | |
| # include <unistd.h> | |
| #endif | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #ifdef HAVE_SYS_IOCTL_H | |
| # include <sys/ioctl.h> | |
| #endif | |
| #ifdef HAVE_SYS_MMAN_H | |
| # include <sys/mman.h> | |
| #endif | |
| #ifdef HAVE_POLL_H | |
| #include <poll.h> | |
| #endif | |
| #ifdef HAVE_SYS_POLL_H | |
| # include <sys/poll.h> | |
| #endif | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "wingdi.h" | |
| #include "winuser.h" | |
| #include "winerror.h" | |
| #include "mmddk.h" | |
| #include "mmreg.h" | |
| #include "dsound.h" | |
| #include "dsdriver.h" | |
| #include "oss.h" | |
| #include "wine/debug.h" | |
| #include "audio.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(wave); | |
| #ifdef HAVE_OSS | |
| /*======================================================================* | |
| * Low level DSOUND definitions * | |
| *======================================================================*/ | |
| typedef struct IDsDriverPropertySetImpl IDsDriverPropertySetImpl; | |
| typedef struct IDsDriverNotifyImpl IDsDriverNotifyImpl; | |
| typedef struct IDsDriverImpl IDsDriverImpl; | |
| typedef struct IDsDriverBufferImpl IDsDriverBufferImpl; | |
| struct IDsDriverPropertySetImpl | |
| { | |
| /* IUnknown fields */ | |
| IDsDriverPropertySetVtbl *lpVtbl; | |
| DWORD ref; | |
| IDsDriverBufferImpl* buffer; | |
| }; | |
| struct IDsDriverNotifyImpl | |
| { | |
| /* IUnknown fields */ | |
| IDsDriverNotifyVtbl *lpVtbl; | |
| DWORD ref; | |
| /* IDsDriverNotifyImpl fields */ | |
| LPDSBPOSITIONNOTIFY notifies; | |
| int nrofnotifies; | |
| IDsDriverBufferImpl* buffer; | |
| }; | |
| struct IDsDriverImpl | |
| { | |
| /* IUnknown fields */ | |
| IDsDriverVtbl *lpVtbl; | |
| DWORD ref; | |
| /* IDsDriverImpl fields */ | |
| UINT wDevID; | |
| IDsDriverBufferImpl* primary; | |
| int nrofsecondaries; | |
| IDsDriverBufferImpl** secondaries; | |
| }; | |
| struct IDsDriverBufferImpl | |
| { | |
| /* IUnknown fields */ | |
| IDsDriverBufferVtbl *lpVtbl; | |
| DWORD ref; | |
| /* IDsDriverBufferImpl fields */ | |
| IDsDriverImpl* drv; | |
| DWORD buflen; | |
| WAVEFORMATPCMEX wfex; | |
| LPBYTE mapping; | |
| DWORD maplen; | |
| int fd; | |
| DWORD dwFlags; | |
| /* IDsDriverNotifyImpl fields */ | |
| IDsDriverNotifyImpl* notify; | |
| int notify_index; | |
| /* IDsDriverPropertySetImpl fields */ | |
| IDsDriverPropertySetImpl* property_set; | |
| }; | |
| static HRESULT WINAPI IDsDriverPropertySetImpl_Create( | |
| IDsDriverBufferImpl * dsdb, | |
| IDsDriverPropertySetImpl **pdsdps); | |
| static HRESULT WINAPI IDsDriverNotifyImpl_Create( | |
| IDsDriverBufferImpl * dsdb, | |
| IDsDriverNotifyImpl **pdsdn); | |
| /*======================================================================* | |
| * Low level DSOUND property set implementation * | |
| *======================================================================*/ | |
| static HRESULT WINAPI IDsDriverPropertySetImpl_QueryInterface( | |
| PIDSDRIVERPROPERTYSET iface, | |
| REFIID riid, | |
| LPVOID *ppobj) | |
| { | |
| IDsDriverPropertySetImpl *This = (IDsDriverPropertySetImpl *)iface; | |
| TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj); | |
| if ( IsEqualGUID(riid, &IID_IUnknown) || | |
| IsEqualGUID(riid, &IID_IDsDriverPropertySet) ) { | |
| IDsDriverPropertySet_AddRef(iface); | |
| *ppobj = (LPVOID)This; | |
| return DS_OK; | |
| } | |
| FIXME( "Unknown IID %s\n", debugstr_guid( riid ) ); | |
| *ppobj = 0; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI IDsDriverPropertySetImpl_AddRef(PIDSDRIVERPROPERTYSET iface) | |
| { | |
| IDsDriverPropertySetImpl *This = (IDsDriverPropertySetImpl *)iface; | |
| ULONG refCount = InterlockedIncrement(&This->ref); | |
| TRACE("(%p) ref was %ld\n", This, refCount - 1); | |
| return refCount; | |
| } | |
| static ULONG WINAPI IDsDriverPropertySetImpl_Release(PIDSDRIVERPROPERTYSET iface) | |
| { | |
| IDsDriverPropertySetImpl *This = (IDsDriverPropertySetImpl *)iface; | |
| ULONG refCount = InterlockedDecrement(&This->ref); | |
| TRACE("(%p) ref was %ld\n", This, refCount + 1); | |
| if (!refCount) { | |
| IDsDriverBuffer_Release((PIDSDRIVERBUFFER)This->buffer); | |
| HeapFree(GetProcessHeap(),0,This); | |
| TRACE("(%p) released\n",This); | |
| } | |
| return refCount; | |
| } | |
| static HRESULT WINAPI IDsDriverPropertySetImpl_Get( | |
| PIDSDRIVERPROPERTYSET iface, | |
| PDSPROPERTY pDsProperty, | |
| LPVOID pPropertyParams, | |
| ULONG cbPropertyParams, | |
| LPVOID pPropertyData, | |
| ULONG cbPropertyData, | |
| PULONG pcbReturnedData ) | |
| { | |
| IDsDriverPropertySetImpl *This = (IDsDriverPropertySetImpl *)iface; | |
| FIXME("(%p,%p,%p,%lx,%p,%lx,%p)\n",This,pDsProperty,pPropertyParams,cbPropertyParams,pPropertyData,cbPropertyData,pcbReturnedData); | |
| return DSERR_UNSUPPORTED; | |
| } | |
| static HRESULT WINAPI IDsDriverPropertySetImpl_Set( | |
| PIDSDRIVERPROPERTYSET iface, | |
| PDSPROPERTY pDsProperty, | |
| LPVOID pPropertyParams, | |
| ULONG cbPropertyParams, | |
| LPVOID pPropertyData, | |
| ULONG cbPropertyData ) | |
| { | |
| IDsDriverPropertySetImpl *This = (IDsDriverPropertySetImpl *)iface; | |
| FIXME("(%p,%p,%p,%lx,%p,%lx)\n",This,pDsProperty,pPropertyParams,cbPropertyParams,pPropertyData,cbPropertyData); | |
| return DSERR_UNSUPPORTED; | |
| } | |
| static HRESULT WINAPI IDsDriverPropertySetImpl_QuerySupport( | |
| PIDSDRIVERPROPERTYSET iface, | |
| REFGUID PropertySetId, | |
| ULONG PropertyId, | |
| PULONG pSupport ) | |
| { | |
| IDsDriverPropertySetImpl *This = (IDsDriverPropertySetImpl *)iface; | |
| FIXME("(%p,%s,%lx,%p)\n",This,debugstr_guid(PropertySetId),PropertyId,pSupport); | |
| return DSERR_UNSUPPORTED; | |
| } | |
| IDsDriverPropertySetVtbl dsdpsvt = | |
| { | |
| IDsDriverPropertySetImpl_QueryInterface, | |
| IDsDriverPropertySetImpl_AddRef, | |
| IDsDriverPropertySetImpl_Release, | |
| IDsDriverPropertySetImpl_Get, | |
| IDsDriverPropertySetImpl_Set, | |
| IDsDriverPropertySetImpl_QuerySupport, | |
| }; | |
| /*======================================================================* | |
| * Low level DSOUND notify implementation * | |
| *======================================================================*/ | |
| static HRESULT WINAPI IDsDriverNotifyImpl_QueryInterface( | |
| PIDSDRIVERNOTIFY iface, | |
| REFIID riid, | |
| LPVOID *ppobj) | |
| { | |
| IDsDriverNotifyImpl *This = (IDsDriverNotifyImpl *)iface; | |
| TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj); | |
| if ( IsEqualGUID(riid, &IID_IUnknown) || | |
| IsEqualGUID(riid, &IID_IDsDriverNotify) ) { | |
| IDsDriverNotify_AddRef(iface); | |
| *ppobj = This; | |
| return DS_OK; | |
| } | |
| FIXME( "Unknown IID %s\n", debugstr_guid( riid ) ); | |
| *ppobj = 0; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI IDsDriverNotifyImpl_AddRef(PIDSDRIVERNOTIFY iface) | |
| { | |
| IDsDriverNotifyImpl *This = (IDsDriverNotifyImpl *)iface; | |
| ULONG refCount = InterlockedIncrement(&This->ref); | |
| TRACE("(%p) ref was %ld\n", This, refCount - 1); | |
| return refCount; | |
| } | |
| static ULONG WINAPI IDsDriverNotifyImpl_Release(PIDSDRIVERNOTIFY iface) | |
| { | |
| IDsDriverNotifyImpl *This = (IDsDriverNotifyImpl *)iface; | |
| ULONG refCount = InterlockedDecrement(&This->ref); | |
| TRACE("(%p) ref was %ld\n", This, refCount + 1); | |
| if (!refCount) { | |
| IDsDriverBuffer_Release((PIDSDRIVERBUFFER)This->buffer); | |
| HeapFree(GetProcessHeap(), 0, This->notifies); | |
| HeapFree(GetProcessHeap(),0,This); | |
| TRACE("(%p) released\n",This); | |
| } | |
| return refCount; | |
| } | |
| static HRESULT WINAPI IDsDriverNotifyImpl_SetNotificationPositions( | |
| PIDSDRIVERNOTIFY iface, | |
| DWORD howmuch, | |
| LPCDSBPOSITIONNOTIFY notify) | |
| { | |
| IDsDriverNotifyImpl *This = (IDsDriverNotifyImpl *)iface; | |
| TRACE("(%p,0x%08lx,%p)\n",This,howmuch,notify); | |
| if (!notify) { | |
| WARN("invalid parameter\n"); | |
| return DSERR_INVALIDPARAM; | |
| } | |
| if (TRACE_ON(wave)) { | |
| int i; | |
| for (i=0;i<howmuch;i++) | |
| TRACE("notify at %ld to 0x%08lx\n", | |
| notify[i].dwOffset,(DWORD)notify[i].hEventNotify); | |
| } | |
| /* Make an internal copy of the caller-supplied array. | |
| * Replace the existing copy if one is already present. */ | |
| if (This->notifies) | |
| This->notifies = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, | |
| This->notifies, howmuch * sizeof(DSBPOSITIONNOTIFY)); | |
| else | |
| This->notifies = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, | |
| howmuch * sizeof(DSBPOSITIONNOTIFY)); | |
| memcpy(This->notifies, notify, howmuch * sizeof(DSBPOSITIONNOTIFY)); | |
| This->nrofnotifies = howmuch; | |
| return S_OK; | |
| } | |
| IDsDriverNotifyVtbl dsdnvt = | |
| { | |
| IDsDriverNotifyImpl_QueryInterface, | |
| IDsDriverNotifyImpl_AddRef, | |
| IDsDriverNotifyImpl_Release, | |
| IDsDriverNotifyImpl_SetNotificationPositions, | |
| }; | |
| /*======================================================================* | |
| * Low level DSOUND implementation * | |
| *======================================================================*/ | |
| static HRESULT DSDB_MapBuffer(IDsDriverBufferImpl *dsdb) | |
| { | |
| TRACE("(%p), format=%ldx%dx%d\n", dsdb, dsdb->wfex.Format.nSamplesPerSec, | |
| dsdb->wfex.Format.wBitsPerSample, dsdb->wfex.Format.nChannels); | |
| if (!dsdb->mapping) { | |
| dsdb->mapping = mmap(NULL, dsdb->maplen, PROT_WRITE, MAP_SHARED, | |
| dsdb->fd, 0); | |
| if (dsdb->mapping == (LPBYTE)-1) { | |
| ERR("Could not map sound device for direct access (%s)\n", strerror(errno)); | |
| ERR("Use: \"HardwareAcceleration\" = \"Emulation\" in the [dsound] section of your config file.\n"); | |
| return DSERR_GENERIC; | |
| } | |
| TRACE("The sound device has been mapped for direct access at %p, size=%ld\n", dsdb->mapping, dsdb->maplen); | |
| /* for some reason, es1371 and sblive! sometimes have junk in here. | |
| * clear it, or we get junk noise */ | |
| /* some libc implementations are buggy: their memset reads from the buffer... | |
| * to work around it, we have to zero the block by hand. We don't do the expected: | |
| * memset(dsdb->mapping,0, dsdb->maplen); | |
| */ | |
| { | |
| unsigned char* p1 = dsdb->mapping; | |
| unsigned len = dsdb->maplen; | |
| unsigned char silence = (dsdb->wfex.Format.wBitsPerSample == 8) ? 128 : 0; | |
| unsigned long ulsilence = (dsdb->wfex.Format.wBitsPerSample == 8) ? 0x80808080 : 0; | |
| if (len >= 16) /* so we can have at least a 4 long area to store... */ | |
| { | |
| /* the mmap:ed value is (at least) dword aligned | |
| * so, start filling the complete unsigned long:s | |
| */ | |
| int b = len >> 2; | |
| unsigned long* p4 = (unsigned long*)p1; | |
| while (b--) *p4++ = ulsilence; | |
| /* prepare for filling the rest */ | |
| len &= 3; | |
| p1 = (unsigned char*)p4; | |
| } | |
| /* in all cases, fill the remaining bytes */ | |
| while (len-- != 0) *p1++ = silence; | |
| } | |
| } | |
| return DS_OK; | |
| } | |
| static HRESULT DSDB_UnmapBuffer(IDsDriverBufferImpl *dsdb) | |
| { | |
| TRACE("(%p)\n",dsdb); | |
| if (dsdb->mapping) { | |
| if (munmap(dsdb->mapping, dsdb->maplen) < 0) { | |
| ERR("(%p): Could not unmap sound device (%s)\n", dsdb, strerror(errno)); | |
| return DSERR_GENERIC; | |
| } | |
| dsdb->mapping = NULL; | |
| TRACE("(%p): sound device unmapped\n", dsdb); | |
| } | |
| return DS_OK; | |
| } | |
| static HRESULT WINAPI IDsDriverBufferImpl_QueryInterface(PIDSDRIVERBUFFER iface, REFIID riid, LPVOID *ppobj) | |
| { | |
| IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; | |
| TRACE("(%p,%s,%p)\n",iface,debugstr_guid(riid),*ppobj); | |
| if ( IsEqualGUID(riid, &IID_IUnknown) || | |
| IsEqualGUID(riid, &IID_IDsDriverBuffer) ) { | |
| IDsDriverBuffer_AddRef(iface); | |
| *ppobj = (LPVOID)This; | |
| return DS_OK; | |
| } | |
| if ( IsEqualGUID( &IID_IDsDriverNotify, riid ) ) { | |
| if (!This->notify) | |
| IDsDriverNotifyImpl_Create(This, &(This->notify)); | |
| if (This->notify) { | |
| IDsDriverNotify_AddRef((PIDSDRIVERNOTIFY)This->notify); | |
| *ppobj = (LPVOID)This->notify; | |
| return DS_OK; | |
| } | |
| *ppobj = 0; | |
| return E_FAIL; | |
| } | |
| if ( IsEqualGUID( &IID_IDsDriverPropertySet, riid ) ) { | |
| if (!This->property_set) | |
| IDsDriverPropertySetImpl_Create(This, &(This->property_set)); | |
| if (This->property_set) { | |
| IDsDriverPropertySet_AddRef((PIDSDRIVERPROPERTYSET)This->property_set); | |
| *ppobj = (LPVOID)This->property_set; | |
| return DS_OK; | |
| } | |
| *ppobj = 0; | |
| return E_FAIL; | |
| } | |
| FIXME( "Unknown IID %s\n", debugstr_guid( riid ) ); | |
| *ppobj = 0; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI IDsDriverBufferImpl_AddRef(PIDSDRIVERBUFFER iface) | |
| { | |
| IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; | |
| ULONG refCount = InterlockedIncrement(&This->ref); | |
| TRACE("(%p) ref was %ld\n", This, refCount - 1); | |
| return refCount; | |
| } | |
| static ULONG WINAPI IDsDriverBufferImpl_Release(PIDSDRIVERBUFFER iface) | |
| { | |
| IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; | |
| ULONG refCount = InterlockedDecrement(&This->ref); | |
| TRACE("(%p) ref was %ld\n", This, refCount + 1); | |
| if (refCount) | |
| return refCount; | |
| if (This == This->drv->primary) | |
| This->drv->primary = NULL; | |
| else { | |
| int i; | |
| for (i = 0; i < This->drv->nrofsecondaries; i++) | |
| if (This->drv->secondaries[i] == This) | |
| break; | |
| if (i < This->drv->nrofsecondaries) { | |
| /* Put the last buffer of the list in the (now empty) position */ | |
| This->drv->secondaries[i] = This->drv->secondaries[This->drv->nrofsecondaries - 1]; | |
| This->drv->nrofsecondaries--; | |
| This->drv->secondaries = HeapReAlloc(GetProcessHeap(),0, | |
| This->drv->secondaries, | |
| sizeof(PIDSDRIVERBUFFER)*This->drv->nrofsecondaries); | |
| TRACE("(%p) buffer count is now %d\n", This, This->drv->nrofsecondaries); | |
| } | |
| WOutDev[This->drv->wDevID].ossdev->ds_caps.dwFreeHwMixingAllBuffers++; | |
| WOutDev[This->drv->wDevID].ossdev->ds_caps.dwFreeHwMixingStreamingBuffers++; | |
| } | |
| DSDB_UnmapBuffer(This); | |
| HeapFree(GetProcessHeap(),0,This); | |
| TRACE("(%p) released\n",This); | |
| return 0; | |
| } | |
| static HRESULT WINAPI IDsDriverBufferImpl_Lock(PIDSDRIVERBUFFER iface, | |
| LPVOID*ppvAudio1,LPDWORD pdwLen1, | |
| LPVOID*ppvAudio2,LPDWORD pdwLen2, | |
| DWORD dwWritePosition,DWORD dwWriteLen, | |
| DWORD dwFlags) | |
| { | |
| /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */ | |
| /* since we (GetDriverDesc flags) have specified DSDDESC_DONTNEEDPRIMARYLOCK, | |
| * and that we don't support secondary buffers, this method will never be called */ | |
| TRACE("(%p): stub\n",iface); | |
| return DSERR_UNSUPPORTED; | |
| } | |
| static HRESULT WINAPI IDsDriverBufferImpl_Unlock(PIDSDRIVERBUFFER iface, | |
| LPVOID pvAudio1,DWORD dwLen1, | |
| LPVOID pvAudio2,DWORD dwLen2) | |
| { | |
| /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */ | |
| TRACE("(%p): stub\n",iface); | |
| return DSERR_UNSUPPORTED; | |
| } | |
| static HRESULT WINAPI IDsDriverBufferImpl_SetFormat(PIDSDRIVERBUFFER iface, | |
| LPWAVEFORMATEX pwfx) | |
| { | |
| /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */ | |
| TRACE("(%p,%p)\n",iface,pwfx); | |
| /* On our request (GetDriverDesc flags), DirectSound has by now used | |
| * waveOutClose/waveOutOpen to set the format... | |
| * unfortunately, this means our mmap() is now gone... | |
| * so we need to somehow signal to our DirectSound implementation | |
| * that it should completely recreate this HW buffer... | |
| * this unexpected error code should do the trick... */ | |
| return DSERR_BUFFERLOST; | |
| } | |
| static HRESULT WINAPI IDsDriverBufferImpl_SetFrequency(PIDSDRIVERBUFFER iface, DWORD dwFreq) | |
| { | |
| /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */ | |
| TRACE("(%p,%ld): stub\n",iface,dwFreq); | |
| return DSERR_UNSUPPORTED; | |
| } | |
| static HRESULT WINAPI IDsDriverBufferImpl_SetVolumePan(PIDSDRIVERBUFFER iface, PDSVOLUMEPAN pVolPan) | |
| { | |
| DWORD vol; | |
| IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; | |
| TRACE("(%p,%p)\n",This,pVolPan); | |
| vol = pVolPan->dwTotalLeftAmpFactor | (pVolPan->dwTotalRightAmpFactor << 16); | |
| if (wodSetVolume(This->drv->wDevID, vol) != MMSYSERR_NOERROR) { | |
| WARN("wodSetVolume failed\n"); | |
| return DSERR_INVALIDPARAM; | |
| } | |
| return DS_OK; | |
| } | |
| static HRESULT WINAPI IDsDriverBufferImpl_SetPosition(PIDSDRIVERBUFFER iface, DWORD dwNewPos) | |
| { | |
| /* IDsDriverImpl *This = (IDsDriverImpl *)iface; */ | |
| TRACE("(%p,%ld): stub\n",iface,dwNewPos); | |
| return DSERR_UNSUPPORTED; | |
| } | |
| static HRESULT WINAPI IDsDriverBufferImpl_GetPosition(PIDSDRIVERBUFFER iface, | |
| LPDWORD lpdwPlay, LPDWORD lpdwWrite) | |
| { | |
| IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; | |
| count_info info; | |
| DWORD ptr; | |
| TRACE("(%p)\n",iface); | |
| if (WOutDev[This->drv->wDevID].state == WINE_WS_CLOSED) { | |
| ERR("device not open, but accessing?\n"); | |
| return DSERR_UNINITIALIZED; | |
| } | |
| if (ioctl(This->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { | |
| ERR("ioctl(%s, SNDCTL_DSP_GETOPTR) failed (%s)\n", | |
| WOutDev[This->drv->wDevID].ossdev->dev_name, strerror(errno)); | |
| return DSERR_GENERIC; | |
| } | |
| ptr = info.ptr & ~3; /* align the pointer, just in case */ | |
| if (lpdwPlay) *lpdwPlay = ptr; | |
| if (lpdwWrite) { | |
| /* add some safety margin (not strictly necessary, but...) */ | |
| if (WOutDev[This->drv->wDevID].ossdev->duplex_out_caps.dwSupport & WAVECAPS_SAMPLEACCURATE) | |
| *lpdwWrite = ptr + 32; | |
| else | |
| *lpdwWrite = ptr + WOutDev[This->drv->wDevID].dwFragmentSize; | |
| while (*lpdwWrite > This->buflen) | |
| *lpdwWrite -= This->buflen; | |
| } | |
| TRACE("playpos=%ld, writepos=%ld\n", lpdwPlay?*lpdwPlay:0, lpdwWrite?*lpdwWrite:0); | |
| return DS_OK; | |
| } | |
| static HRESULT WINAPI IDsDriverBufferImpl_Play(PIDSDRIVERBUFFER iface, DWORD dwRes1, DWORD dwRes2, DWORD dwFlags) | |
| { | |
| IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; | |
| int enable; | |
| TRACE("(%p,%lx,%lx,%lx)\n",iface,dwRes1,dwRes2,dwFlags); | |
| WOutDev[This->drv->wDevID].ossdev->bOutputEnabled = TRUE; | |
| enable = getEnables(WOutDev[This->drv->wDevID].ossdev); | |
| if (ioctl(This->fd, SNDCTL_DSP_SETTRIGGER, &enable) < 0) { | |
| if (errno == EINVAL) { | |
| /* Don't give up yet. OSS trigger support is inconsistent. */ | |
| if (WOutDev[This->drv->wDevID].ossdev->open_count == 1) { | |
| /* try the opposite input enable */ | |
| if (WOutDev[This->drv->wDevID].ossdev->bInputEnabled == FALSE) | |
| WOutDev[This->drv->wDevID].ossdev->bInputEnabled = TRUE; | |
| else | |
| WOutDev[This->drv->wDevID].ossdev->bInputEnabled = FALSE; | |
| /* try it again */ | |
| enable = getEnables(WOutDev[This->drv->wDevID].ossdev); | |
| if (ioctl(This->fd, SNDCTL_DSP_SETTRIGGER, &enable) >= 0) | |
| return DS_OK; | |
| } | |
| } | |
| ERR("ioctl(%s, SNDCTL_DSP_SETTRIGGER) failed (%s)\n", | |
| WOutDev[This->drv->wDevID].ossdev->dev_name, strerror(errno)); | |
| WOutDev[This->drv->wDevID].ossdev->bOutputEnabled = FALSE; | |
| return DSERR_GENERIC; | |
| } | |
| return DS_OK; | |
| } | |
| static HRESULT WINAPI IDsDriverBufferImpl_Stop(PIDSDRIVERBUFFER iface) | |
| { | |
| IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; | |
| int enable; | |
| TRACE("(%p)\n",iface); | |
| /* no more playing */ | |
| WOutDev[This->drv->wDevID].ossdev->bOutputEnabled = FALSE; | |
| enable = getEnables(WOutDev[This->drv->wDevID].ossdev); | |
| if (ioctl(This->fd, SNDCTL_DSP_SETTRIGGER, &enable) < 0) { | |
| ERR("ioctl(%s, SNDCTL_DSP_SETTRIGGER) failed (%s)\n", WOutDev[This->drv->wDevID].ossdev->dev_name, strerror(errno)); | |
| return DSERR_GENERIC; | |
| } | |
| #if 0 | |
| /* the play position must be reset to the beginning of the buffer */ | |
| if (ioctl(This->fd, SNDCTL_DSP_RESET, 0) < 0) { | |
| ERR("ioctl(%s, SNDCTL_DSP_RESET) failed (%s)\n", WOutDev[This->drv->wDevID].ossdev->dev_name, strerror(errno)); | |
| return DSERR_GENERIC; | |
| } | |
| #endif | |
| /* Most OSS drivers just can't stop the playback without closing the device... | |
| * so we need to somehow signal to our DirectSound implementation | |
| * that it should completely recreate this HW buffer... | |
| * this unexpected error code should do the trick... */ | |
| /* FIXME: ...unless we are doing full duplex, then it's not nice to close the device */ | |
| if (WOutDev[This->drv->wDevID].ossdev->open_count == 1) | |
| return DSERR_BUFFERLOST; | |
| return DS_OK; | |
| } | |
| static IDsDriverBufferVtbl dsdbvt = | |
| { | |
| IDsDriverBufferImpl_QueryInterface, | |
| IDsDriverBufferImpl_AddRef, | |
| IDsDriverBufferImpl_Release, | |
| IDsDriverBufferImpl_Lock, | |
| IDsDriverBufferImpl_Unlock, | |
| IDsDriverBufferImpl_SetFormat, | |
| IDsDriverBufferImpl_SetFrequency, | |
| IDsDriverBufferImpl_SetVolumePan, | |
| IDsDriverBufferImpl_SetPosition, | |
| IDsDriverBufferImpl_GetPosition, | |
| IDsDriverBufferImpl_Play, | |
| IDsDriverBufferImpl_Stop | |
| }; | |
| static HRESULT WINAPI IDsDriverImpl_QueryInterface(PIDSDRIVER iface, REFIID riid, LPVOID *ppobj) | |
| { | |
| IDsDriverImpl *This = (IDsDriverImpl *)iface; | |
| TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj); | |
| if ( IsEqualGUID(riid, &IID_IUnknown) || | |
| IsEqualGUID(riid, &IID_IDsDriver) ) { | |
| IDsDriver_AddRef(iface); | |
| *ppobj = (LPVOID)This; | |
| return DS_OK; | |
| } | |
| FIXME( "Unknown IID %s\n", debugstr_guid( riid ) ); | |
| *ppobj = 0; | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI IDsDriverImpl_AddRef(PIDSDRIVER iface) | |
| { | |
| IDsDriverImpl *This = (IDsDriverImpl *)iface; | |
| ULONG refCount = InterlockedIncrement(&This->ref); | |
| TRACE("(%p) ref was %ld\n", This, refCount - 1); | |
| return refCount; | |
| } | |
| static ULONG WINAPI IDsDriverImpl_Release(PIDSDRIVER iface) | |
| { | |
| IDsDriverImpl *This = (IDsDriverImpl *)iface; | |
| ULONG refCount = InterlockedDecrement(&This->ref); | |
| TRACE("(%p) ref was %ld\n", This, refCount + 1); | |
| if (!refCount) { | |
| HeapFree(GetProcessHeap(),0,This); | |
| TRACE("(%p) released\n",This); | |
| } | |
| return refCount; | |
| } | |
| static HRESULT WINAPI IDsDriverImpl_GetDriverDesc(PIDSDRIVER iface, | |
| PDSDRIVERDESC pDesc) | |
| { | |
| IDsDriverImpl *This = (IDsDriverImpl *)iface; | |
| TRACE("(%p,%p)\n",iface,pDesc); | |
| /* copy version from driver */ | |
| memcpy(pDesc, &(WOutDev[This->wDevID].ossdev->ds_desc), sizeof(DSDRIVERDESC)); | |
| pDesc->dwFlags |= DSDDESC_DOMMSYSTEMOPEN | DSDDESC_DOMMSYSTEMSETFORMAT | | |
| DSDDESC_USESYSTEMMEMORY | DSDDESC_DONTNEEDPRIMARYLOCK | | |
| DSDDESC_DONTNEEDSECONDARYLOCK; | |
| pDesc->dnDevNode = WOutDev[This->wDevID].waveDesc.dnDevNode; | |
| pDesc->wVxdId = 0; | |
| pDesc->wReserved = 0; | |
| pDesc->ulDeviceNum = This->wDevID; | |
| pDesc->dwHeapType = DSDHEAP_NOHEAP; | |
| pDesc->pvDirectDrawHeap = NULL; | |
| pDesc->dwMemStartAddress = 0; | |
| pDesc->dwMemEndAddress = 0; | |
| pDesc->dwMemAllocExtra = 0; | |
| pDesc->pvReserved1 = NULL; | |
| pDesc->pvReserved2 = NULL; | |
| return DS_OK; | |
| } | |
| static HRESULT WINAPI IDsDriverImpl_Open(PIDSDRIVER iface) | |
| { | |
| IDsDriverImpl *This = (IDsDriverImpl *)iface; | |
| int enable; | |
| TRACE("(%p)\n",iface); | |
| /* make sure the card doesn't start playing before we want it to */ | |
| WOutDev[This->wDevID].ossdev->bOutputEnabled = FALSE; | |
| WOutDev[This->wDevID].ossdev->bInputEnabled = FALSE; | |
| enable = getEnables(WOutDev[This->wDevID].ossdev); | |
| if (ioctl(WOutDev[This->wDevID].ossdev->fd, SNDCTL_DSP_SETTRIGGER, &enable) < 0) { | |
| ERR("ioctl(%s, SNDCTL_DSP_SETTRIGGER) failed (%s)\n",WOutDev[This->wDevID].ossdev->dev_name, strerror(errno)); | |
| return DSERR_GENERIC; | |
| } | |
| return DS_OK; | |
| } | |
| static HRESULT WINAPI IDsDriverImpl_Close(PIDSDRIVER iface) | |
| { | |
| IDsDriverImpl *This = (IDsDriverImpl *)iface; | |
| TRACE("(%p)\n",iface); | |
| if (This->primary) { | |
| ERR("problem with DirectSound: primary not released\n"); | |
| return DSERR_GENERIC; | |
| } | |
| return DS_OK; | |
| } | |
| static HRESULT WINAPI IDsDriverImpl_GetCaps(PIDSDRIVER iface, PDSDRIVERCAPS pCaps) | |
| { | |
| IDsDriverImpl *This = (IDsDriverImpl *)iface; | |
| TRACE("(%p,%p)\n",iface,pCaps); | |
| memcpy(pCaps, &(WOutDev[This->wDevID].ossdev->ds_caps), sizeof(DSDRIVERCAPS)); | |
| return DS_OK; | |
| } | |
| static HRESULT WINAPI DSD_CreatePrimaryBuffer(PIDSDRIVER iface, | |
| LPWAVEFORMATEX pwfx, | |
| DWORD dwFlags, | |
| DWORD dwCardAddress, | |
| LPDWORD pdwcbBufferSize, | |
| LPBYTE *ppbBuffer, | |
| LPVOID *ppvObj) | |
| { | |
| IDsDriverImpl *This = (IDsDriverImpl *)iface; | |
| IDsDriverBufferImpl** ippdsdb = (IDsDriverBufferImpl**)ppvObj; | |
| HRESULT err; | |
| audio_buf_info info; | |
| int enable = 0; | |
| TRACE("(%p,%p,%lx,%lx,%p,%p,%p)\n",iface,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj); | |
| if (This->primary) | |
| return DSERR_ALLOCATED; | |
| if (dwFlags & (DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPAN)) | |
| return DSERR_CONTROLUNAVAIL; | |
| *ippdsdb = (IDsDriverBufferImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDsDriverBufferImpl)); | |
| if (*ippdsdb == NULL) | |
| return DSERR_OUTOFMEMORY; | |
| (*ippdsdb)->lpVtbl = &dsdbvt; | |
| (*ippdsdb)->ref = 1; | |
| (*ippdsdb)->drv = This; | |
| copy_format(pwfx, &(*ippdsdb)->wfex); | |
| (*ippdsdb)->fd = WOutDev[This->wDevID].ossdev->fd; | |
| (*ippdsdb)->dwFlags = dwFlags; | |
| /* check how big the DMA buffer is now */ | |
| if (ioctl((*ippdsdb)->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { | |
| ERR("ioctl(%s, SNDCTL_DSP_GETOSPACE) failed (%s)\n", | |
| WOutDev[This->wDevID].ossdev->dev_name, strerror(errno)); | |
| HeapFree(GetProcessHeap(),0,*ippdsdb); | |
| *ippdsdb = NULL; | |
| return DSERR_GENERIC; | |
| } | |
| (*ippdsdb)->maplen = (*ippdsdb)->buflen = info.fragstotal * info.fragsize; | |
| /* map the DMA buffer */ | |
| err = DSDB_MapBuffer(*ippdsdb); | |
| if (err != DS_OK) { | |
| HeapFree(GetProcessHeap(),0,*ippdsdb); | |
| *ippdsdb = NULL; | |
| return err; | |
| } | |
| /* primary buffer is ready to go */ | |
| *pdwcbBufferSize = (*ippdsdb)->maplen; | |
| *ppbBuffer = (*ippdsdb)->mapping; | |
| /* some drivers need some extra nudging after mapping */ | |
| WOutDev[This->wDevID].ossdev->bInputEnabled = FALSE; | |
| WOutDev[This->wDevID].ossdev->bOutputEnabled = FALSE; | |
| enable = getEnables(WOutDev[This->wDevID].ossdev); | |
| if (ioctl((*ippdsdb)->fd, SNDCTL_DSP_SETTRIGGER, &enable) < 0) { | |
| ERR("ioctl(%s, SNDCTL_DSP_SETTRIGGER) failed (%s)\n", | |
| WOutDev[This->wDevID].ossdev->dev_name, strerror(errno)); | |
| return DSERR_GENERIC; | |
| } | |
| This->primary = *ippdsdb; | |
| return DS_OK; | |
| } | |
| static HRESULT WINAPI DSD_CreateSecondaryBuffer(PIDSDRIVER iface, | |
| LPWAVEFORMATEX pwfx, | |
| DWORD dwFlags, | |
| DWORD dwCardAddress, | |
| LPDWORD pdwcbBufferSize, | |
| LPBYTE *ppbBuffer, | |
| LPVOID *ppvObj) | |
| { | |
| IDsDriverImpl *This = (IDsDriverImpl *)iface; | |
| IDsDriverBufferImpl** ippdsdb = (IDsDriverBufferImpl**)ppvObj; | |
| FIXME("(%p,%p,%lx,%lx,%p,%p,%p): stub\n",This,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj); | |
| *ippdsdb = 0; | |
| return DSERR_UNSUPPORTED; | |
| } | |
| static HRESULT WINAPI IDsDriverImpl_CreateSoundBuffer(PIDSDRIVER iface, | |
| LPWAVEFORMATEX pwfx, | |
| DWORD dwFlags, | |
| DWORD dwCardAddress, | |
| LPDWORD pdwcbBufferSize, | |
| LPBYTE *ppbBuffer, | |
| LPVOID *ppvObj) | |
| { | |
| TRACE("(%p,%p,%lx,%lx,%p,%p,%p)\n",iface,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj); | |
| if (dwFlags & DSBCAPS_PRIMARYBUFFER) | |
| return DSD_CreatePrimaryBuffer(iface,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj); | |
| return DSD_CreateSecondaryBuffer(iface,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj); | |
| } | |
| static HRESULT WINAPI IDsDriverImpl_DuplicateSoundBuffer(PIDSDRIVER iface, | |
| PIDSDRIVERBUFFER pBuffer, | |
| LPVOID *ppvObj) | |
| { | |
| /* IDsDriverImpl *This = (IDsDriverImpl *)iface; */ | |
| TRACE("(%p,%p): stub\n",iface,pBuffer); | |
| return DSERR_INVALIDCALL; | |
| } | |
| static IDsDriverVtbl dsdvt = | |
| { | |
| IDsDriverImpl_QueryInterface, | |
| IDsDriverImpl_AddRef, | |
| IDsDriverImpl_Release, | |
| IDsDriverImpl_GetDriverDesc, | |
| IDsDriverImpl_Open, | |
| IDsDriverImpl_Close, | |
| IDsDriverImpl_GetCaps, | |
| IDsDriverImpl_CreateSoundBuffer, | |
| IDsDriverImpl_DuplicateSoundBuffer | |
| }; | |
| static HRESULT WINAPI IDsDriverPropertySetImpl_Create( | |
| IDsDriverBufferImpl * dsdb, | |
| IDsDriverPropertySetImpl **pdsdps) | |
| { | |
| IDsDriverPropertySetImpl * dsdps; | |
| TRACE("(%p,%p)\n",dsdb,pdsdps); | |
| dsdps = (IDsDriverPropertySetImpl*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(dsdps)); | |
| if (dsdps == NULL) { | |
| WARN("out of memory\n"); | |
| return DSERR_OUTOFMEMORY; | |
| } | |
| dsdps->ref = 0; | |
| dsdps->lpVtbl = &dsdpsvt; | |
| dsdps->buffer = dsdb; | |
| dsdb->property_set = dsdps; | |
| IDsDriverBuffer_AddRef((PIDSDRIVER)dsdb); | |
| *pdsdps = dsdps; | |
| return DS_OK; | |
| } | |
| static HRESULT WINAPI IDsDriverNotifyImpl_Create( | |
| IDsDriverBufferImpl * dsdb, | |
| IDsDriverNotifyImpl **pdsdn) | |
| { | |
| IDsDriverNotifyImpl * dsdn; | |
| TRACE("(%p,%p)\n",dsdb,pdsdn); | |
| dsdn = (IDsDriverNotifyImpl*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(dsdn)); | |
| if (dsdn == NULL) { | |
| WARN("out of memory\n"); | |
| return DSERR_OUTOFMEMORY; | |
| } | |
| dsdn->ref = 0; | |
| dsdn->lpVtbl = &dsdnvt; | |
| dsdn->buffer = dsdb; | |
| dsdb->notify = dsdn; | |
| IDsDriverBuffer_AddRef((PIDSDRIVER)dsdb); | |
| *pdsdn = dsdn; | |
| return DS_OK; | |
| } | |
| DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv) | |
| { | |
| IDsDriverImpl** idrv = (IDsDriverImpl**)drv; | |
| TRACE("(%d,%p)\n",wDevID,drv); | |
| /* the HAL isn't much better than the HEL if we can't do mmap() */ | |
| if (!(WOutDev[wDevID].ossdev->duplex_out_caps.dwSupport & WAVECAPS_DIRECTSOUND)) { | |
| ERR("DirectSound flag not set\n"); | |
| MESSAGE("This sound card's driver does not support direct access\n"); | |
| MESSAGE("The (slower) DirectSound HEL mode will be used instead.\n"); | |
| return MMSYSERR_NOTSUPPORTED; | |
| } | |
| *idrv = (IDsDriverImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDsDriverImpl)); | |
| if (!*idrv) | |
| return MMSYSERR_NOMEM; | |
| (*idrv)->lpVtbl = &dsdvt; | |
| (*idrv)->ref = 1; | |
| (*idrv)->wDevID = wDevID; | |
| (*idrv)->primary = NULL; | |
| (*idrv)->nrofsecondaries = 0; | |
| (*idrv)->secondaries = NULL; | |
| return MMSYSERR_NOERROR; | |
| } | |
| DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc) | |
| { | |
| TRACE("(%d,%p)\n",wDevID,desc); | |
| memcpy(desc, &(WOutDev[wDevID].ossdev->ds_desc), sizeof(DSDRIVERDESC)); | |
| return MMSYSERR_NOERROR; | |
| } | |
| #endif /* HAVE_OSS */ |