/*
 * Copyright (c) 2010-2019 Belledonne Communications SARL.
 *
 * This file is part of mediastreamer2.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef __DIRECTSOUND_ENABLED__

#define UNICODE

#include "mediastreamer2/mssndcard.h"
#include "mediastreamer2/msfilter.h"
#include "mediastreamer2/msticker.h"

#include <mmsystem.h>
#ifdef _MSC_VER
#include <mmreg.h>
#endif
#include <msacm.h>

#include <dsound.h>
#include <dsconf.h>

const GUID GUID_DSCFX_MS_AEC = {0xcdebb919, 0x379a, 0x488a, {0x87, 0x65, 0xf5, 0x3c, 0xfd, 0x36, 0xde, 0x40}};
const GUID GUID_DSCFX_CLASS_AEC = {0xBF963D80L, 0xC559, 0x11D0, {0x8A, 0x2B, 0x00, 0xA0, 0xC9, 0x25, 0x5A, 0xC1}}; 

const GUID CLSID_DirectSoundPrivate= { 0x11ab3ec0, 0x25ec, 0x11d1, {0xa4, 0xd8, 0x0, 0xc0, 0x4f, 0xc2, 0x8a, 0xca}};
const GUID DSPROPSETID_DirectSoundDevice = {0x84624f82, 0x25ec, 0x11d1, {0xa4, 0xd8, 0x0, 0xc0, 0x4f, 0xc2, 0x8a, 0xca}};

#define WINSNDDS_MINIMUMBUFFER 5

static MSFilter *ms_winsndds_read_new(MSSndCard *card);
static MSFilter *ms_winsndds_write_new(MSSndCard *card);

static HMODULE ms_lib_instance=NULL;
static HRESULT (WINAPI *ms_DllGetClassObject)(REFCLSID , REFIID , LPVOID *);

static HRESULT (WINAPI *ms_DirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
static HRESULT (WINAPI *ms_DirectSoundEnumerate)(LPDSENUMCALLBACKW, LPVOID);

static HRESULT (WINAPI *ms_DirectSoundCaptureCreate)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN);
static HRESULT (WINAPI *ms_DirectSoundCaptureEnumerate)(LPDSENUMCALLBACKW, LPVOID);

static HRESULT (WINAPI *ms_DirectSoundFullDuplexCreate)(LPCGUID , LPCGUID ,
														LPCDSCBUFFERDESC , LPCDSBUFFERDESC , HWND ,
														DWORD , LPDIRECTSOUNDFULLDUPLEX* , LPDIRECTSOUNDCAPTUREBUFFER8 *,
														LPDIRECTSOUNDBUFFER8 *, LPUNKNOWN );

typedef struct WinSndDsCard{
	int in_devid;
	int out_devid;
	GUID in_guid;
	GUID out_guid;
	int removed;
}WinSndDsCard;

static BOOL GetWaveIdFromDSoundGUID( GUID i_sGUID, DWORD *dwWaveID)
{ 
    LPKSPROPERTYSET         pKsPropertySet = NULL; 
    LPCLASSFACTORY          pClassFactory  = NULL; 
    HRESULT                 hr;
	BOOL					retval = FALSE;

	PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA psDirectSoundDeviceDescription = NULL; 
	DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA sDirectSoundDeviceDescription;

	memset(&sDirectSoundDeviceDescription,0,sizeof(sDirectSoundDeviceDescription)); 

    hr = ms_DllGetClassObject (CLSID_DirectSoundPrivate, IID_IClassFactory, (LPVOID *)&pClassFactory );

    if(SUCCEEDED(hr)) 
    { 
        hr = pClassFactory->CreateInstance ( NULL, IID_IKsPropertySet, (LPVOID *)&pKsPropertySet ); 
    } 

    // Release the class factory 
    if(pClassFactory) 
    { 
        pClassFactory->Release(); 
    }

    if(SUCCEEDED(hr)) 
	{ 
		ULONG ulBytesReturned = 0;
		sDirectSoundDeviceDescription.DeviceId = i_sGUID; 

		// On the first call the final size is unknown so pass the size of the struct in order to receive
		// "Type" and "DataFlow" values, ulBytesReturned will be populated with bytes required for struct+strings.
        hr = pKsPropertySet->Get(DSPROPSETID_DirectSoundDevice, 
                DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION, 
                NULL, 
                0, 
                &sDirectSoundDeviceDescription, 
                sizeof(sDirectSoundDeviceDescription), 
                &ulBytesReturned
            ); 

		if (ulBytesReturned)
		{
			// On the first call it notifies us of the required amount of memory in order to receive the strings.
			// Allocate the required memory, the strings will be pointed to the memory space directly after the struct.
			psDirectSoundDeviceDescription = (PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA)new BYTE[ulBytesReturned];
			*psDirectSoundDeviceDescription = sDirectSoundDeviceDescription;

			hr = pKsPropertySet->Get(DSPROPSETID_DirectSoundDevice, 
					DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION, 
					NULL, 
					0, 
					psDirectSoundDeviceDescription, 
					ulBytesReturned, 
					&ulBytesReturned
				); 

			*dwWaveID  = psDirectSoundDeviceDescription->WaveDeviceId;
			delete [] psDirectSoundDeviceDescription;
			retval = TRUE;
		}

		pKsPropertySet->Release(); 
	} 

	return retval; 
} 

static void winsnddscard_set_level(MSSndCard *card, MSSndCardMixerElem e, int percent){
	WinSndDsCard *d=(WinSndDsCard*)card->data;
	DWORD waveout_devid = WAVE_MAPPER;
	DWORD wavein_devid = WAVE_MAPPER;

	UINT uMixerID;
	DWORD dwMixerHandle;
	MIXERLINE MixerLine;
	MIXERLINE Line;
	UINT uLineIndex;

	MIXERLINECONTROLS mlc = {0};
	MIXERCONTROL mc = {0};
	MIXERCONTROLDETAILS mcd = {0};
	MIXERCONTROLDETAILS_UNSIGNED mcdu = {0};

	MMRESULT mr = MMSYSERR_NOERROR;
	DWORD dwVolume = ((0xFFFF) * percent) / 100;
	
	WORD wLeftVol, wRightVol;
	DWORD dwNewVol;
	wLeftVol = LOWORD(dwVolume); // get higher WORD
	wRightVol = LOWORD(dwVolume); // get lower WORD

	dwNewVol = MAKELONG(wLeftVol, wRightVol);

	GetWaveIdFromDSoundGUID(d->in_guid, &wavein_devid);
	GetWaveIdFromDSoundGUID(d->out_guid, &waveout_devid);

	switch(e){
		case MS_SND_CARD_PLAYBACK:
		case MS_SND_CARD_MASTER:
			{

				mr = mixerGetID( (HMIXEROBJ)waveout_devid, &uMixerID, MIXER_OBJECTF_WAVEOUT );
				if ( mr != MMSYSERR_NOERROR )
				{
					ms_error("winsndcard_set_level: mixerGetID failed. (0x%x)", mr);
					return;
				}
				mr = mixerOpen( (LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L );
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_set_level: mixerOpen failed. (0x%x)", mr);
					return;
				}
				memset( &MixerLine, 0, sizeof(MIXERLINE) );
				MixerLine.cbStruct = sizeof(MIXERLINE);
				if (MS_SND_CARD_MASTER==e)
					MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
				else
					MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
				mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE );
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_set_level: mixerGetLineInfo failed. (0x%x)", mr);
					return;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				mlc.cbStruct = sizeof(MIXERLINECONTROLS);
				mlc.dwLineID = MixerLine.dwLineID;
				mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
				mlc.cControls = 1;
				mlc.pamxctrl = &mc;
				mlc.cbmxctrl = sizeof(MIXERCONTROL);
				mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, 
					&mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);

				if (mr != MMSYSERR_NOERROR)
				{
					ms_error("winsndcard_set_level: mixerGetLineControls failed. (0x%x)", mr);
					return;
				}

				mcdu.dwValue = 65535*percent/100; /* the volume is a number between 0 and 65535 */

				mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
				mcd.hwndOwner = 0;
				mcd.dwControlID = mc.dwControlID;
				mcd.paDetails = &mcdu;
				mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
				mcd.cChannels = 1;
				mr = mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, 
					&mcd, MIXER_SETCONTROLDETAILSF_VALUE);

				if (mr != MMSYSERR_NOERROR)
				{
					ms_error("winsndcard_set_level: mixerSetControlDetails failed. (0x%x)", mr);
					return;
				}
			}
			break;
		case MS_SND_CARD_CAPTURE:
			mr = mixerGetID( (HMIXEROBJ)wavein_devid, &uMixerID, MIXER_OBJECTF_WAVEIN );
			if ( mr != MMSYSERR_NOERROR )
			{
				ms_error("winsndcard_set_level: mixerGetID failed. (0x%x)", mr);
				return;
			}
			mr = mixerOpen( (LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L );			
			if ( mr != MMSYSERR_NOERROR )
			{
				mixerClose( (HMIXER)dwMixerHandle );
				ms_error("winsndcard_set_level: mixerGetLineInfo failed. (0x%x)", mr);
				return;
			}
			memset( &MixerLine, 0, sizeof(MIXERLINE) );
			MixerLine.cbStruct = sizeof(MIXERLINE);
			MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
			mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE );
			if ( mr != MMSYSERR_NOERROR )
			{
				mixerClose( (HMIXER)dwMixerHandle );
				ms_error("winsndcard_set_level: mixerGetLineInfo failed. (0x%x)", mr);
				return;
			}

			/* ms_message("Name: %s\n", MixerLine.szName); */
			/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
			/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

			mlc.cbStruct = sizeof(MIXERLINECONTROLS);
			mlc.dwLineID = MixerLine.dwLineID;
			mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
			mlc.cControls = 1;
			mlc.pamxctrl = &mc;
			mlc.cbmxctrl = sizeof(MIXERCONTROL);
			mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, 
				&mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);

			if (mr == MMSYSERR_NOERROR)
			{
				mcdu.dwValue = 65535*percent/100; /* the volume is a number between 0 and 65535 */

				mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
				mcd.hwndOwner = 0;
				mcd.dwControlID = mc.dwControlID;
				mcd.paDetails = &mcdu;
				mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
				mcd.cChannels = 1;
				mr = mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, 
					&mcd, MIXER_SETCONTROLDETAILSF_VALUE);

				if (mr == MMSYSERR_NOERROR)
				{
					return;
				}
				ms_error("winsndcard_set_level: mixerSetControlDetails failed. (0x%x)", mr);
				ms_warning("winsndcard_set_level: control the SRC_MICROPHONE instead");
			}
			else
			{
				ms_error("winsndcard_set_level: mixerGetLineControls failed. (0x%x)", mr);
				ms_warning("winsndcard_set_level: control the SRC_MICROPHONE instead");
			}

			/* In case capture doesn't work: use the SRC_MICROPHONE volume */

			for (uLineIndex = 0; uLineIndex < MixerLine.cConnections; uLineIndex++)
			{
				memset( &Line, 0, sizeof(MIXERLINE) );
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_LINEID);
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_set_level: mixerGetLineInfo failed. (0x%x)", mr);
					return;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				memset( &Line, 0, sizeof(MIXERLINE) );
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_SOURCE);
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_set_level: mixerGetLineInfo failed. (0x%x)", mr);
					return;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == Line.dwComponentType)  
				{
					LPMIXERCONTROL pmxctrl = (LPMIXERCONTROL)malloc(sizeof(MIXERCONTROL));
					MIXERLINECONTROLS mxlctrl = {sizeof(mxlctrl), Line.dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME, 1, sizeof(MIXERCONTROL), pmxctrl};
					mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mxlctrl, MIXER_GETLINECONTROLSF_ONEBYTYPE);
					if(!mr){
						DWORD cChannels = Line.cChannels;
						LPMIXERCONTROLDETAILS_UNSIGNED pUnsigned;
						MIXERCONTROLDETAILS mxcd;
						if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)  
							cChannels = 1;  
						pUnsigned =  
							(LPMIXERCONTROLDETAILS_UNSIGNED)  
							malloc(cChannels * sizeof(MIXERCONTROLDETAILS_UNSIGNED));

						mxcd.cbStruct = sizeof(mxcd);
						mxcd.dwControlID = pmxctrl->dwControlID;
						mxcd.cChannels = cChannels;
						mxcd.hwndOwner = (HWND)0;
						mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
						mxcd.paDetails = (LPVOID) pUnsigned;

						mr = mixerGetControlDetails((HMIXEROBJ)dwMixerHandle, &mxcd,  
							MIXER_SETCONTROLDETAILSF_VALUE);  
						if(!mr){
							pUnsigned[0].dwValue = pUnsigned[cChannels - 1].dwValue
								=  pmxctrl->Bounds.dwMaximum*percent/100;
							mr = mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, &mxcd,  
								MIXER_SETCONTROLDETAILSF_VALUE);  
							if(mr){
								ms_error("winsndcard_set_level: mixerSetControlDetails failed. (0x%x)", mr);
							}
						}
						else  
						{
							ms_error("winsndcard_set_level: mixerGetControlDetails failed. (0x%x)", mr);
						}
						free(pmxctrl);  
						free(pUnsigned);
					}  
					else  
					{
						ms_error("winsndcard_set_level: mixerGetLineControls failed. (0x%x)", mr);
						free(pmxctrl);
					}
				}
			}
			mixerClose( (HMIXER)dwMixerHandle );
			if (mr != MMSYSERR_NOERROR)
			{
				ms_error("winsndcard_set_level: mixerClose failed. (0x%x)", mr);
				return;
			}
			break;
		default:
			ms_warning("winsnd_card_set_level: unsupported command.");
	}
}

static int winsnddscard_get_level(MSSndCard *card, MSSndCardMixerElem e){
	WinSndDsCard *d=(WinSndDsCard*)card->data;
	DWORD waveout_devid = WAVE_MAPPER;
	DWORD wavein_devid = WAVE_MAPPER;

	UINT uMixerID;
	DWORD dwMixerHandle;
	MIXERLINE MixerLine;
	MIXERLINE Line;
	UINT uLineIndex;

	MIXERLINECONTROLS mlc = {0};
	MIXERCONTROL mc = {0};
	MIXERCONTROLDETAILS mcd = {0};
	MIXERCONTROLDETAILS_UNSIGNED mcdu = {0};

	MMRESULT mr = MMSYSERR_NOERROR;
	int percent;

	GetWaveIdFromDSoundGUID(d->in_guid, &wavein_devid);
	GetWaveIdFromDSoundGUID(d->out_guid, &waveout_devid);

	switch(e){
		case MS_SND_CARD_MASTER:
		case MS_SND_CARD_PLAYBACK:
			{
				mr = mixerGetID( (HMIXEROBJ)waveout_devid, &uMixerID, MIXER_OBJECTF_WAVEOUT );
				if ( mr != MMSYSERR_NOERROR )
				{
					ms_error("winsndcard_get_level: mixerGetID failed. (0x%x)", mr);
					return -1;
				}
				mr = mixerOpen( (LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L );
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_get_level: mixerOpen failed. (0x%x)", mr);
					return -1;
				}
				memset( &MixerLine, 0, sizeof(MIXERLINE) );
				MixerLine.cbStruct = sizeof(MIXERLINE);
				if (MS_SND_CARD_MASTER==e)
					MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
				else
					MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
				mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE );
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_get_level: mixerGetLineInfo failed. (0x%x)", mr);
					return -1;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				mlc.cbStruct = sizeof(MIXERLINECONTROLS);
				mlc.dwLineID = MixerLine.dwLineID;
				mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
				mlc.cControls = 1;
				mlc.pamxctrl = &mc;
				mlc.cbmxctrl = sizeof(MIXERCONTROL);
				mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, 
					&mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
				if (mr != MMSYSERR_NOERROR)
				{
					ms_error("winsndcard_get_level: mixerGetLineControls failed. (0x%x)", mr);
					return -1;
				}

				mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
				mcd.hwndOwner = 0;
				mcd.dwControlID = mc.dwControlID;
				mcd.paDetails = &mcdu;
				mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
				mcd.cChannels = 1;
				mr = mixerGetControlDetails((HMIXEROBJ)dwMixerHandle, &mcd,  
					MIXER_SETCONTROLDETAILSF_VALUE);  
				percent = (mcdu.dwValue *100) / (mc.Bounds.dwMaximum);

				if (mr != MMSYSERR_NOERROR)
				{
					ms_error("winsndcard_get_level: mixerGetControlDetails failed. (0x%x)", mr);
					return -1;
				}
				return percent;
			}
			break;
		case MS_SND_CARD_CAPTURE:
			mr = mixerGetID( (HMIXEROBJ)wavein_devid, &uMixerID, MIXER_OBJECTF_WAVEIN );
			if ( mr != MMSYSERR_NOERROR )
			{
				ms_error("winsndcard_get_level: mixerGetID failed. (0x%x)", mr);
				return -1;
			}
			mr = mixerOpen( (LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L );
			if ( mr != MMSYSERR_NOERROR )
			{
				mixerClose( (HMIXER)dwMixerHandle );
				ms_error("winsndcard_get_level: mixerOpen failed. (0x%x)", mr);
				return -1;
			}
			memset( &MixerLine, 0, sizeof(MIXERLINE) );
			MixerLine.cbStruct = sizeof(MIXERLINE);
			MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
			mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE );
			if ( mr != MMSYSERR_NOERROR )
			{
				mixerClose( (HMIXER)dwMixerHandle );
				ms_error("winsndcard_get_level: mixerGetLineInfo failed. (0x%x)", mr);
				return -1;
			}

			/* ms_message("Name: %s\n", MixerLine.szName); */
			/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
			/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

			mlc.cbStruct = sizeof(MIXERLINECONTROLS);
			mlc.dwLineID = MixerLine.dwLineID;
			mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
			mlc.cControls = 1;
			mlc.pamxctrl = &mc;
			mlc.cbmxctrl = sizeof(MIXERCONTROL);
			mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, 
				&mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
			if (mr == MMSYSERR_NOERROR)
			{
				mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
				mcd.hwndOwner = 0;
				mcd.dwControlID = mc.dwControlID;
				mcd.paDetails = &mcdu;
				mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
				mcd.cChannels = 1;
				mr = mixerGetControlDetails((HMIXEROBJ)dwMixerHandle, &mcd,  
					MIXER_SETCONTROLDETAILSF_VALUE);  
				percent = (mcdu.dwValue *100) / (mc.Bounds.dwMaximum);

				if (mr == MMSYSERR_NOERROR)
				{
					return percent;
				}
				ms_error("winsndcard_get_level: mixerGetControlDetails failed. (0x%x)", mr);
				ms_warning("winsndcard_get_level: control the SRC_MICROPHONE instead");
			}
			else
			{
				ms_error("winsndcard_get_level: mixerGetLineControls failed. (0x%x)", mr);
				ms_warning("winsndcard_get_level: control the SRC_MICROPHONE instead");
			}

			for (uLineIndex = 0; uLineIndex < MixerLine.cConnections; uLineIndex++)
			{
				memset( &Line, 0, sizeof(MIXERLINE) );
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_LINEID);
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_get_level: mixerGetLineInfo failed. (0x%x)", mr);
					return -1;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				memset( &Line, 0, sizeof(MIXERLINE) );
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_SOURCE);
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_get_level: mixerGetLineInfo failed. (0x%x)", mr);
					return -1;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == Line.dwComponentType)
				{
					LPMIXERCONTROL pmxctrl = (LPMIXERCONTROL)malloc(sizeof(MIXERCONTROL));
					MIXERLINECONTROLS mxlctrl = {sizeof(mxlctrl), Line.dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME, 1, sizeof(MIXERCONTROL), pmxctrl};  
					if(!mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mxlctrl,  
						MIXER_GETLINECONTROLSF_ONEBYTYPE)){  
							DWORD cChannels = Line.cChannels;
							LPMIXERCONTROLDETAILS_UNSIGNED pUnsigned;
							MIXERCONTROLDETAILS mxcd;
							if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)  
								cChannels = 1;  
							pUnsigned =  
								(LPMIXERCONTROLDETAILS_UNSIGNED)  
								malloc(cChannels * sizeof(MIXERCONTROLDETAILS_UNSIGNED));

							mxcd.cbStruct = sizeof(mxcd);
							mxcd.dwControlID = pmxctrl->dwControlID;
							mxcd.cChannels = cChannels;
							mxcd.hwndOwner = (HWND)0;
							mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
							mxcd.paDetails = (LPVOID) pUnsigned;

							mixerGetControlDetails((HMIXEROBJ)dwMixerHandle, &mxcd,  
								MIXER_SETCONTROLDETAILSF_VALUE);  
							percent = (pUnsigned[0].dwValue *100) / (pmxctrl->Bounds.dwMaximum);
							free(pmxctrl);
							free(pUnsigned);
					}  
					else  
						free(pmxctrl);  
				}
			}
			mixerClose( (HMIXER)dwMixerHandle );
			if (mr != MMSYSERR_NOERROR)
			{
				ms_error("winsndcard_get_level: mixerClose failed. (0x%x)", mr);
				return -1;
			}
			return percent;
			break;
		default:
			ms_warning("winsndcard_get_level: unsupported command.");
			return -1;
	}
	return -1;
}

static void winsnddscard_set_source(MSSndCard *card, MSSndCardCapture source){

	switch(source){
		case MS_SND_CARD_MIC:
			break;
		case MS_SND_CARD_LINE:
			break;
	}	
}

static int winsnddscard_set_control(MSSndCard *card, MSSndCardControlElem e, int val){
	WinSndDsCard *d=(WinSndDsCard*)card->data;
	DWORD waveout_devid = WAVE_MAPPER;
	DWORD wavein_devid = WAVE_MAPPER;

	UINT uMixerID;
	DWORD dwMixerHandle;
	MIXERLINE MixerLine;
	MIXERLINE Line;
	UINT uLineIndex;

	MIXERLINECONTROLS mlc = {0};
	MIXERCONTROL mc = {0};
	MIXERCONTROLDETAILS mcd = {0};
	MIXERCONTROLDETAILS_BOOLEAN bMute;

	MMRESULT mr = MMSYSERR_NOERROR;

	GetWaveIdFromDSoundGUID(d->in_guid, &wavein_devid);
	GetWaveIdFromDSoundGUID(d->out_guid, &waveout_devid);

	switch(e){
		case MS_SND_CARD_CAPTURE_MUTE:
			bMute.fValue = (val>0);

			mr = mixerGetID( (HMIXEROBJ)wavein_devid, &uMixerID, MIXER_OBJECTF_WAVEIN );
			if ( mr != MMSYSERR_NOERROR )
			{
				ms_error("winsndcard_set_control: mixerGetID failed. (0x%x)", mr);
				return -1;
			}
			mr = mixerOpen( (LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L );
			if ( mr != MMSYSERR_NOERROR )
			{
				mixerClose( (HMIXER)dwMixerHandle );
				ms_error("winsndcard_set_control: mixerOpen failed. (0x%x)", mr);
				return -1;
			}
			memset( &MixerLine, 0, sizeof(MIXERLINE) );
			MixerLine.cbStruct = sizeof(MIXERLINE);
			MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
			mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE );
			if ( mr != MMSYSERR_NOERROR )
			{
				mixerClose( (HMIXER)dwMixerHandle );
				ms_error("winsndcard_set_control: mixerGetLineInfo failed. (0x%x)", mr);
				return -1;
			}
			/* ms_message("Name: %s\n", MixerLine.szName); */
			/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
			/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

			mlc.cbStruct = sizeof(MIXERLINECONTROLS);
			mlc.dwLineID = MixerLine.dwLineID;
			mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; //MIXERCONTROL_CONTROLTYPE_VOLUME;
			mlc.cControls = 1;
			mlc.pamxctrl = &mc;
			mlc.cbmxctrl = sizeof(MIXERCONTROL);
			mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, 
				&mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
			if (mr == MMSYSERR_NOERROR)
			{
				mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
				mcd.hwndOwner = 0;
				mcd.dwControlID = mc.dwControlID;
				mcd.paDetails = &bMute;
				mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
				mcd.cChannels = 1;
				mr = mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, 
					&mcd, MIXER_SETCONTROLDETAILSF_VALUE);

				if (mr == MMSYSERR_NOERROR)
				{
					return 0;
				}
				ms_error("winsndcard_set_control: mixerSetControlDetails failed. (0x%x)", mr);
				ms_warning("winsndcard_get_level: control the SRC_MICROPHONE instead");
			}
			else
			{
				ms_error("winsndcard_set_control: mixerGetLineControls failed. (0x%x)", mr);
				ms_warning("winsndcard_get_level: control the SRC_MICROPHONE instead");
			}


			for (uLineIndex = 0; uLineIndex < MixerLine.cConnections; uLineIndex++)
			{
				memset( &Line, 0, sizeof(MIXERLINE) );
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_LINEID);
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_set_control: mixerGetLineInfo failed. (0x%x)", mr);
					return -1;
				}
				
				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				memset( &Line, 0, sizeof(MIXERLINE) );
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_SOURCE);
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_set_control: mixerGetLineInfo failed. (0x%x)", mr);
					return -1;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == Line.dwComponentType)
				{
					/* unmute */
					/* Find a mute control, if any, of the microphone line  */

					LPMIXERCONTROL pmxctrl = (LPMIXERCONTROL)malloc(sizeof(MIXERCONTROL));
					MIXERLINECONTROLS mxlctrl = {sizeof(mxlctrl), Line.dwLineID, MIXERCONTROL_CONTROLTYPE_MUTE, 1, sizeof(MIXERCONTROL), pmxctrl};  
					if(!mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mxlctrl, MIXER_GETLINECONTROLSF_ONEBYTYPE)){  
						DWORD cChannels = Line.cChannels;
						LPMIXERCONTROLDETAILS_BOOLEAN pbool;
						MIXERCONTROLDETAILS mxcd;

						if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)  
							cChannels = 1;  
						pbool = (LPMIXERCONTROLDETAILS_BOOLEAN) malloc(cChannels * sizeof(
							MIXERCONTROLDETAILS_BOOLEAN));

						mxcd.cbStruct = sizeof(mxcd);
						mxcd.dwControlID = pmxctrl->dwControlID;
						mxcd.cChannels = cChannels;
						mxcd.hwndOwner = (HWND)0;
						mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
						mxcd.paDetails = (LPVOID) pbool;

						mixerGetControlDetails((HMIXEROBJ)dwMixerHandle, &mxcd,  
							MIXER_SETCONTROLDETAILSF_VALUE);  
						/* Unmute the microphone line (for both channels) */
						pbool[0].fValue = pbool[cChannels - 1].fValue = val; /* 0 -> unmute; */
						mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, &mxcd,  
							MIXER_SETCONTROLDETAILSF_VALUE);  
						free(pmxctrl);  
						free(pbool);  
					}  
					else  
						free(pmxctrl);  
				}
			}
			mixerClose( (HMIXER)dwMixerHandle );
			if (mr != MMSYSERR_NOERROR)
			{
				ms_error("winsndcard_set_control: mixerClose failed. (0x%x)", mr);
				return -1;
			}
			return 0;

		case MS_SND_CARD_MASTER_MUTE:
		case MS_SND_CARD_PLAYBACK_MUTE:
			{
				bMute.fValue = (val>0);

				mr = mixerGetID( (HMIXEROBJ)waveout_devid, &uMixerID, MIXER_OBJECTF_WAVEOUT );
				if ( mr != MMSYSERR_NOERROR )
				{
					ms_error("winsndcard_set_control: mixerGetID failed. (0x%x)", mr);
					return -1;
				}
				mr = mixerOpen( (LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L );
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_set_control: mixerOpen failed. (0x%x)", mr);
					return -1;
				}
				memset( &MixerLine, 0, sizeof(MIXERLINE) );
				MixerLine.cbStruct = sizeof(MIXERLINE);
				if (MS_SND_CARD_MASTER_MUTE==e)
					MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
				else
					MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
				mr = mixerGetLineInfo( (HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE );
				if ( mr != MMSYSERR_NOERROR )
				{
					mixerClose( (HMIXER)dwMixerHandle );
					ms_error("winsndcard_set_control: mixerSetControlDetails failed. (0x%x)", mr);
					return -1;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				mlc.cbStruct = sizeof(MIXERLINECONTROLS);
				mlc.dwLineID = MixerLine.dwLineID;
				mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; //MIXERCONTROL_CONTROLTYPE_VOLUME;
				mlc.cControls = 1;
				mlc.pamxctrl = &mc;
				mlc.cbmxctrl = sizeof(MIXERCONTROL);
				mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, 
					&mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);

				mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
				mcd.hwndOwner = 0;
				mcd.dwControlID = mc.dwControlID;
				mcd.paDetails = &bMute;
				mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
				mcd.cChannels = 1;
				mr = mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, 
					&mcd, MIXER_SETCONTROLDETAILSF_VALUE);

				if (mr != MMSYSERR_NOERROR)
				{
					ms_error("winsndcard_set_control: mixerSetControlDetails failed. (0x%x)", mr);
					return -1;
				}
				return 0;
			}
			break;
		default:
			ms_warning("winsndcard_set_control: unsupported command.");
	}
	return -1;
}

static int winsnddscard_get_control(MSSndCard *card, MSSndCardControlElem e){
	WinSndDsCard *d=(WinSndDsCard*)card->data;
	return -1;
}

static void winsnddscard_init(MSSndCard *card){
	WinSndDsCard *c=(WinSndDsCard *)ms_new(WinSndDsCard,1);
	c->removed=0;
	card->data=c;
}

static void winsnddscard_uninit(MSSndCard *card){
	ms_free(card->data);
}

static void winsnddscard_detect(MSSndCardManager *m);
static  MSSndCard *winsnddscard_dup(MSSndCard *obj);
static void winsnddscard_unload(MSSndCardManager *m);

MSSndCardDesc winsndds_card_desc={
	"DirectSound",
	winsnddscard_detect,
	winsnddscard_init,
	winsnddscard_set_level,
	winsnddscard_get_level,
	winsnddscard_set_source,
	winsnddscard_set_control,
	NULL,
	ms_winsndds_read_new,
	ms_winsndds_write_new,
	winsnddscard_uninit,
	winsnddscard_dup,
	winsnddscard_unload
};

static  MSSndCard *winsnddscard_dup(MSSndCard *obj){
	MSSndCard *card=ms_snd_card_new(&winsndds_card_desc);
	card->name=ms_strdup(obj->name);
	card->data=ms_new(WinSndDsCard,1);
	memcpy(card->data,obj->data,sizeof(WinSndDsCard));
	return card;
}

static MSSndCard *winsnddscard_new(const char *name, LPGUID lpguid, int in_dev, int out_dev, unsigned cap){
	MSSndCard *card=ms_snd_card_new(&winsndds_card_desc);
	WinSndDsCard *d=(WinSndDsCard*)card->data;
	card->name=ms_strdup(name);
	d->in_devid=in_dev;
	d->out_devid=out_dev;
	card->capabilities=cap;
	memset(&d->out_guid, 0, sizeof(GUID));
	memset(&d->in_guid, 0, sizeof(GUID));
	if (out_dev!=-1)
	{
		if (lpguid!=NULL)
			memcpy(&d->out_guid, lpguid, sizeof(GUID));
	}
	else
	{
		if (lpguid!=NULL)
			memcpy(&d->in_guid, lpguid, sizeof(GUID));
	}
	return card;
}

static void add_or_update_card(MSSndCardManager *m, const char *name, LPGUID lpguid, int indev, int outdev, unsigned int capability){
	MSSndCard *card;
	const bctbx_list_t *elem=ms_snd_card_manager_get_list(m);
	for(;elem!=NULL;elem=elem->next){
		card=(MSSndCard*)elem->data;
		if (strcmp(card->desc->driver_type, winsndds_card_desc.driver_type)==0
			&& strcmp(card->name,name)==0){
			/*update already entered card */
			WinSndDsCard *d=(WinSndDsCard*)card->data;
			card->capabilities|=capability;
			if (indev!=-1) 
				d->in_devid=indev;
			if (outdev!=-1)
				d->out_devid=outdev;

			if (outdev!=-1)
			{
				if (lpguid!=NULL)
					memcpy(&d->out_guid, lpguid, sizeof(GUID));
				else
					memset(&d->out_guid, 0, sizeof(GUID));
			}
			if (indev!=-1)
			{
				if (lpguid!=NULL)
					memcpy(&d->in_guid, lpguid, sizeof(GUID));
				else
					memset(&d->in_guid, 0, sizeof(GUID));
			}
			/* if (d->in_devid!=-1 && d->out_devid!=-1) */
			/*	ms_message("DS: new full duplex card %s", name); */
			d->removed=0;
			return;
		}
	}
	/* add this new card:*/
	ms_snd_card_manager_add_card(m,winsnddscard_new(name,lpguid, indev,outdev,capability));
}

static BOOL CALLBACK enumerate_capture_devices_callback(LPGUID lpGUID,
														LPCWSTR lpszDesc,
														LPCWSTR lpszDrvName,
														LPVOID lpContext )
{
	MSSndCardManager *m = (MSSndCardManager*)lpContext;
	static int dev_index=0;

	if ( lpGUID == NULL ) /* primary device */
	{
		char szName[256];
		wchar_t snd_card_name[256];
		swprintf(snd_card_name, 256, L"%s", lpszDesc);
		WideCharToMultiByte(CP_UTF8,0,snd_card_name,-1,szName,256,0,0);

		add_or_update_card(m,szName,lpGUID,dev_index,-1,MS_SND_CARD_CAP_CAPTURE);
		dev_index++;
	}
	else
	{
		char szName[256];
		wchar_t snd_card_name[256];
		swprintf(snd_card_name, 256, L"%s", lpszDesc);
		WideCharToMultiByte(CP_UTF8,0,snd_card_name,-1,szName,256,0,0);

		add_or_update_card(m,szName,lpGUID,dev_index,-1,MS_SND_CARD_CAP_CAPTURE);
		dev_index++;
	}

	return true;
}

static BOOL CALLBACK enumerate_playback_devices_callback(LPGUID lpGUID,
														 LPCWSTR lpszDesc,
														 LPCWSTR lpszDrvName,
														 LPVOID lpContext )
{
	MSSndCardManager *m = (MSSndCardManager*)lpContext;
	static int dev_index=0;

	if ( lpGUID == NULL ) /* primary device */
	{
		char szName[256];
		wchar_t snd_card_name[256];
		swprintf(snd_card_name, 256, L"%s", lpszDesc);
		WideCharToMultiByte(CP_UTF8,0,snd_card_name,-1,szName,256,0,0);

		add_or_update_card(m,szName,lpGUID,-1,dev_index,MS_SND_CARD_CAP_PLAYBACK);
		dev_index++;
	}
	else
	{
		char szName[256];
		wchar_t snd_card_name[256];
		swprintf(snd_card_name, 256, L"%s", lpszDesc);
		WideCharToMultiByte(CP_UTF8,0,snd_card_name,-1,szName,256,0,0);

		add_or_update_card(m,szName,lpGUID,-1,dev_index,MS_SND_CARD_CAP_PLAYBACK);
		dev_index++;
	}

	return true;
}

static void _winsnddscard_detect(MSSndCardManager *m){
	MMRESULT mr = NOERROR;

	if (ms_lib_instance==NULL)
	{
		ms_lib_instance = LoadLibrary(L"dsound.dll");
		if( ms_lib_instance == NULL )
		{
			/* error */
			ms_debug("winsnddscard_detect: no support for dsound (missing dsound.dll)\n");
			return;
		}

		ms_DllGetClassObject =(HRESULT (WINAPI *)(REFCLSID, REFIID , LPVOID *))
			GetProcAddress( ms_lib_instance, "DllGetClassObject" );

		ms_DirectSoundCreate =(HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN))
			GetProcAddress( ms_lib_instance, "DirectSoundCreate" );

		ms_DirectSoundEnumerate =(HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID))
			GetProcAddress( ms_lib_instance, "DirectSoundEnumerateW" );

		ms_DirectSoundCaptureCreate =(HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN))
			GetProcAddress( ms_lib_instance, "DirectSoundCaptureCreate" );

		ms_DirectSoundCaptureEnumerate =(HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID))
			GetProcAddress( ms_lib_instance, "DirectSoundCaptureEnumerateW" );

		ms_DirectSoundFullDuplexCreate =(HRESULT (WINAPI *)(LPCGUID , LPCGUID ,
			LPCDSCBUFFERDESC , LPCDSBUFFERDESC , HWND ,
			DWORD , LPDIRECTSOUNDFULLDUPLEX* , LPDIRECTSOUNDCAPTUREBUFFER8 *,
			LPDIRECTSOUNDBUFFER8 *, LPUNKNOWN))
			GetProcAddress( ms_lib_instance, "DirectSoundFullDuplexCreate" );

		if( ms_DllGetClassObject == NULL ||
			ms_DirectSoundCreate == NULL ||
			ms_DirectSoundEnumerate == NULL ||
			ms_DirectSoundCaptureEnumerate == NULL ||
			ms_DirectSoundCaptureCreate == NULL )
		{
			/* error */
			ms_debug("winsnddscard_detect: no support for dsound\n");
			return;
		}
	}

	ms_DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)enumerate_capture_devices_callback, (void *)m );
	ms_DirectSoundEnumerate( (LPDSENUMCALLBACK)enumerate_playback_devices_callback, (void *)m );
}

static void deactivate_removed_cards(MSSndCardManager *m){
	MSSndCard *card;
	const bctbx_list_t *elem=ms_snd_card_manager_get_list(m);
	for(;elem!=NULL;elem=elem->next){
		card=(MSSndCard*)elem->data;
		if (strcmp(card->desc->driver_type, winsndds_card_desc.driver_type)==0){
			/*mark all cards as potentially removed, detect will check them immediately after */
			WinSndDsCard *d=(WinSndDsCard*)card->data;
			if (d->removed)	card->capabilities=0;
		}
	}
}

static void mark_as_removed(MSSndCardManager *m){
	MSSndCard *card;
	const bctbx_list_t *elem=ms_snd_card_manager_get_list(m);
	for(;elem!=NULL;elem=elem->next){
		card=(MSSndCard*)elem->data;
		if (strcmp(card->desc->driver_type, winsndds_card_desc.driver_type)==0){
			/*mark all cards as potentially removed, detect will check them immediately after */
			WinSndDsCard *d=(WinSndDsCard*)card->data;
			d->removed=1;
		}
	}
}

static ms_thread_t poller_thread=NULL;
static bool_t poller_running=TRUE;

static void * new_device_polling_thread(void *ignore){
	MSSndCardManager *m;
	/*check for new devices every 2 seconds*/
	while(poller_running){
		ms_sleep(5);
		if (poller_running){
			m=ms_snd_card_manager_get();
			if(!m) break;
			mark_as_removed(m);
			_winsnddscard_detect(m);
			deactivate_removed_cards(m);
		}
	}
	return NULL;
}

static void stop_poller(){
	poller_running=FALSE;
	ms_thread_join(poller_thread,NULL);
	poller_thread=NULL;
}

static void winsnddscard_unload(MSSndCardManager *m){
	stop_poller();
}

static void winsnddscard_detect(MSSndCardManager *m){
	_winsnddscard_detect(m);
	if (poller_thread==NULL)
		ms_thread_create(&poller_thread,NULL,new_device_polling_thread,NULL);
}

typedef struct WinSndDs{
	int dev_id;
	GUID in_guid;
	GUID out_guid;

	ms_thread_t thread;
	ms_mutex_t thread_lock;
	ms_cond_t thread_cond;
	bool_t thread_running;

	MSBufferizer output_buff;
	LPDIRECTSOUNDFULLDUPLEX lpDirectSoundFullDuplex;
	LPDIRECTSOUND lpDirectSound;
	LPDIRECTSOUNDBUFFER  lpDirectSoundOutputBuffer;
	double               dsw_framesWritten;
	UINT                 writeOffset;      /* last read position */

	LPDIRECTSOUNDCAPTURE lpDirectSoundCapture;
	LPDIRECTSOUNDCAPTUREBUFFER  lpDirectSoundInputBuffer;
	UINT                 readOffset;      /* last read position */

	int              framesPerDSBuffer;

	WAVEFORMATEX wfx;
	queue_t rq;
	ms_mutex_t mutex;
	uint64_t bytes_read;
	unsigned int nbufs_playing;

	int32_t stat_input;
	int32_t stat_output;
	int32_t stat_notplayed;

}WinSndDs;

void *  
winsndds_read_thread(void *arg)
{
	WinSndDs *d=(WinSndDs*)arg;

	ms_mutex_lock(&d->thread_lock);
	ms_cond_signal(&d->thread_cond);
	ms_mutex_unlock(&d->thread_lock);

	while(d->thread_running)
	{
		HRESULT hr;
		DWORD capturePos;
		DWORD readPos;
		long filled = 0;
		long bytesFilled = 0;
		LPBYTE            lpInBuf1 = NULL;
		LPBYTE            lpInBuf2 = NULL;
		DWORD             dwInSize1 = 0;
		DWORD             dwInSize2 = 0;

		hr = IDirectSoundCaptureBuffer_GetCurrentPosition( d->lpDirectSoundInputBuffer,
			&capturePos, &readPos );
		if( hr != DS_OK )
		{
			continue;
		}

		filled = readPos - d->readOffset;
		if( filled < 0 ) filled += d->framesPerDSBuffer;
		bytesFilled = filled;

		hr = IDirectSoundCaptureBuffer_Lock ( d->lpDirectSoundInputBuffer,
			d->readOffset, bytesFilled,
			(void **) &lpInBuf1, &dwInSize1,
			(void **) &lpInBuf2, &dwInSize2, 0);
		if (hr != DS_OK)
		{
			Sleep(10);
			continue;
		}

		if (dwInSize1==0)
		{
			Sleep(10);
		}
		else if (dwInSize1>=bytesFilled)
		{
			mblk_t *m=allocb(bytesFilled,0);
			memcpy(m->b_rptr, lpInBuf1, bytesFilled);
			m->b_wptr+=bytesFilled;
			ms_mutex_lock(&d->mutex);
			putq(&d->rq,m);
			ms_mutex_unlock(&d->mutex);
			d->bytes_read+=bytesFilled;
			/* ms_message("bytesFilled=%i\n",bytesFilled); */
		}
		else
		{
			mblk_t *m=allocb(bytesFilled,0);
			memcpy(m->b_rptr, lpInBuf1, dwInSize1);
			memcpy(m->b_rptr+dwInSize1, lpInBuf2, dwInSize2);
			m->b_wptr+=bytesFilled;
			ms_mutex_lock(&d->mutex);
			putq(&d->rq,m);
			ms_mutex_unlock(&d->mutex);
			d->bytes_read+=bytesFilled;
			/* ms_message("bytesFilled=%i\n",bytesFilled); */
		}

		d->readOffset = (d->readOffset + bytesFilled) % d->framesPerDSBuffer;

		IDirectSoundCaptureBuffer_Unlock( d->lpDirectSoundInputBuffer,
			lpInBuf1, dwInSize1, lpInBuf2, dwInSize2);
	}

	ms_mutex_lock(&d->thread_lock);
	ms_cond_signal(&d->thread_cond);
	ms_mutex_unlock(&d->thread_lock);
	ms_thread_exit(NULL);
	return NULL;
}

static void winsndds_apply_settings(WinSndDs *d){
	d->wfx.nBlockAlign=d->wfx.nChannels*d->wfx.wBitsPerSample/8;
	d->wfx.nAvgBytesPerSec=d->wfx.nSamplesPerSec*d->wfx.nBlockAlign;
}

static uint64_t winsndds_get_cur_time( void *data){
	WinSndDs *d=(WinSndDs*)data;
	uint64_t curtime=(d->bytes_read*1000)/(uint64_t)d->wfx.nAvgBytesPerSec;
	return curtime;
}


static void winsndds_init(MSFilter *f){
	WinSndDs *d=(WinSndDs *)ms_new0(WinSndDs,1);
	d->wfx.wFormatTag = WAVE_FORMAT_PCM;
	d->wfx.cbSize = 0;
	d->wfx.nAvgBytesPerSec = 16000;
	d->wfx.nBlockAlign = 2;
	d->wfx.nChannels = 1;
	d->wfx.nSamplesPerSec = 8000;
	d->wfx.wBitsPerSample = 16;
	qinit(&d->rq);
	ms_mutex_init(&d->mutex,NULL);
	f->data=d;

	d->stat_input=0;
	d->stat_output=0;
	d->stat_notplayed=0;

	d->framesPerDSBuffer = 320 * (8000 / 1000);

	d->thread = NULL;
	ms_mutex_init(&d->thread_lock,NULL);
	ms_cond_init(&d->thread_cond,NULL);
	d->thread_running = FALSE;

	ms_bufferizer_init(&d->output_buff);
}

static void winsndds_uninit(MSFilter *f){
	WinSndDs *d=(WinSndDs*)f->data;

	d->thread = NULL;
	d->thread_running = FALSE;
	ms_cond_destroy(&d->thread_cond);
	ms_mutex_destroy(&d->thread_lock);
	ms_bufferizer_uninit(&d->output_buff);

	flushq(&d->rq,0);
	ms_mutex_destroy(&d->mutex);
	ms_free(f->data);
}

static void winsndds_read_preprocess(MSFilter *f){
	WinSndDs *d=(WinSndDs*)f->data;
	DSCBUFFERDESC  captureDesc;
	HRESULT hr;

	d->stat_input=0;
	d->stat_output=0;
	d->stat_notplayed=0;

	d->framesPerDSBuffer = d->wfx.nAvgBytesPerSec/4;
	winsndds_apply_settings(d);
	ms_message("full duplex and echo canceller! (%x)" ,d->lpDirectSound);
	ms_DirectSoundCaptureCreate( &d->in_guid, &d->lpDirectSoundCapture, NULL );

	if (d->lpDirectSoundCapture==NULL)
		return;

	ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
	captureDesc.dwSize = sizeof(DSCBUFFERDESC);
	captureDesc.dwFlags =  0;
	captureDesc.dwBufferBytes = d->framesPerDSBuffer;
	captureDesc.lpwfxFormat = &d->wfx;

	if ((hr = IDirectSoundCapture_CreateCaptureBuffer( d->lpDirectSoundCapture,
		&captureDesc, &d->lpDirectSoundInputBuffer, NULL)) != DS_OK)
	{
		return;
	}
	d->readOffset = 0;

	hr = IDirectSoundCaptureBuffer_Start( d->lpDirectSoundInputBuffer, DSCBSTART_LOOPING );

	ms_mutex_lock(&f->ticker->lock);
	ms_ticker_set_time_func(f->ticker,winsndds_get_cur_time,d);
	ms_mutex_unlock(&f->ticker->lock);

	d->thread_running=TRUE;
	ms_thread_create(&d->thread,NULL,winsndds_read_thread,d);
	ms_mutex_lock(&d->thread_lock);
	ms_cond_wait(&d->thread_cond,&d->thread_lock);
	ms_mutex_unlock(&d->thread_lock);

	return;
}

static void winsndds_read_postprocess(MSFilter *f){
	WinSndDs *d=(WinSndDs*)f->data;

	if (d->thread_running==TRUE)
	{
		ms_mutex_lock(&d->thread_lock);
		d->thread_running=FALSE;
		ms_cond_wait(&d->thread_cond,&d->thread_lock);
		ms_mutex_unlock(&d->thread_lock);
		ms_thread_join(d->thread,NULL);

		ms_mutex_lock(&f->ticker->lock);
		ms_ticker_set_time_func(f->ticker,NULL,NULL);
		ms_mutex_unlock(&f->ticker->lock);
	}

	if( d->lpDirectSoundInputBuffer )
	{
		IDirectSoundCaptureBuffer_Stop( d->lpDirectSoundInputBuffer );
		IDirectSoundCaptureBuffer_Release( d->lpDirectSoundInputBuffer );
		d->lpDirectSoundInputBuffer = NULL;
	}

	if( d->lpDirectSoundCapture )
	{
		IDirectSoundCapture_Release( d->lpDirectSoundCapture );
		d->lpDirectSoundCapture = NULL;
	}

	ms_message("Shutting down sound device (playing: %i) (input-output: %i) (notplayed: %i)", d->nbufs_playing, d->stat_input - d->stat_output, d->stat_notplayed);
	flushq(&d->rq,0);
}

static void winsndds_read_process(MSFilter *f){
	WinSndDs *d=(WinSndDs*)f->data;
	mblk_t *m;

	ms_mutex_lock(&d->mutex);
	while((m=getq(&d->rq))!=NULL){
		ms_queue_put(f->outputs[0],m);
	}
	ms_mutex_unlock(&d->mutex);
}

static void winsndds_write_preprocess(MSFilter *f){
	WinSndDs *d=(WinSndDs*)f->data;

	DWORD          dwDataLen;
	DWORD          playCursor;
	HWND           hWnd;
	HRESULT        hr;
	LPDIRECTSOUNDBUFFER pPrimaryBuffer;
	DSBUFFERDESC   primaryDesc;
	DSBUFFERDESC   secondaryDesc;
	unsigned char* pDSBuffData;
	DWORD outputBufferWriteOffsetBytes;

	bctbx_list_t *filters=NULL;
	MSFilter *f_capture_filter=NULL;
	WinSndDs *d_capture_filter=NULL;

	filters=ms_filter_find_neighbours(f);
	if (filters!=NULL)
	{
		bctbx_list_t *it;
		/* search for another winsndds filter */
		for(it=filters;it!=NULL;it=it->next)
		{
			f_capture_filter = (MSFilter*)it->data;
			if (f_capture_filter->desc->id == MS_WINSNDDS_READ_ID)
			{
				/* found */
				d_capture_filter=(WinSndDs*)f_capture_filter->data;
				break;
			}
			f_capture_filter=NULL;
		}
		bctbx_list_free(filters);
	}

	d->stat_input=0;
	d->stat_output=0;
	d->stat_notplayed=0;

	d->framesPerDSBuffer = d->wfx.nAvgBytesPerSec/4;
	winsndds_apply_settings(d);

	if (d_capture_filter!=NULL
		&& d_capture_filter->lpDirectSoundCapture!=NULL
		&& IsEqualIID(d_capture_filter->in_guid, d->in_guid))
	{
		DSCBUFFERDESC captureDesc;

		winsndds_read_postprocess(f_capture_filter);

		DSCEFFECTDESC dscfx[1];
		ZeroMemory( &dscfx[0], sizeof( DSCEFFECTDESC ) );
		dscfx[0].dwSize = sizeof( DSCEFFECTDESC );
		dscfx[0].dwFlags = DSCFX_LOCSOFTWARE ;
		dscfx[0].guidDSCFXClass = GUID_DSCFX_CLASS_AEC;
		dscfx[0].guidDSCFXInstance = GUID_DSCFX_MS_AEC;
		dscfx[0].dwReserved1 = 0;
		dscfx[0].dwReserved2 = 0;

		d_capture_filter->framesPerDSBuffer = d_capture_filter->wfx.nAvgBytesPerSec/4;
		winsndds_apply_settings(d_capture_filter);

		ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
		captureDesc.dwSize = sizeof(DSCBUFFERDESC);
		captureDesc.dwFlags =  DSCBCAPS_CTRLFX;
		captureDesc.dwBufferBytes = d_capture_filter->framesPerDSBuffer;
		captureDesc.lpwfxFormat = &d_capture_filter->wfx;
		captureDesc.dwFXCount = 1;
		captureDesc.lpDSCFXDesc = dscfx;

		ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC));
		secondaryDesc.dwSize = sizeof(DSBUFFERDESC);
		secondaryDesc.dwFlags =  DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2
			| DSBCAPS_LOCSOFTWARE;
		secondaryDesc.dwBufferBytes = d->framesPerDSBuffer;
		secondaryDesc.lpwfxFormat = &d->wfx;

		hWnd = GetDesktopWindow();
		hr = ms_DirectSoundFullDuplexCreate(&d_capture_filter->in_guid,
			&d->out_guid,
			&captureDesc,
			&secondaryDesc,
			hWnd,
			DSSCL_NORMAL,
			&d->lpDirectSoundFullDuplex,
			(LPDIRECTSOUNDCAPTUREBUFFER8*)&d_capture_filter->lpDirectSoundInputBuffer,
			(LPDIRECTSOUNDBUFFER8*)&d->lpDirectSoundOutputBuffer,
			NULL);

		if (hr!=DS_OK)
		{
			ms_message("full duplex and echo canceller: disabled!");
			captureDesc.dwFlags =  0;
			captureDesc.dwFXCount = 0;
			captureDesc.lpDSCFXDesc = NULL;

			hr = ms_DirectSoundFullDuplexCreate(&d_capture_filter->in_guid,
				&d->out_guid,
				&captureDesc,
				&secondaryDesc,
				hWnd,
				DSSCL_NORMAL,
				&d->lpDirectSoundFullDuplex,
				(LPDIRECTSOUNDCAPTUREBUFFER8*)&d_capture_filter->lpDirectSoundInputBuffer,
				(LPDIRECTSOUNDBUFFER8*)&d->lpDirectSoundOutputBuffer,
				NULL);
		}
		if (hr!=DS_OK)
		{
			ms_message("full duplex and echo canceller: disabled!");
			return;
		}
		ms_message("full duplex and echo canceller: activated!");

		d_capture_filter->readOffset = 0;

		hr = IDirectSoundCaptureBuffer_Start( d_capture_filter->lpDirectSoundInputBuffer, DSCBSTART_LOOPING );

		ms_mutex_lock(&f->ticker->lock);
		ms_ticker_set_time_func(f_capture_filter->ticker,winsndds_get_cur_time,d_capture_filter);
		ms_mutex_unlock(&f->ticker->lock);

		d_capture_filter->thread_running=TRUE;
		ms_thread_create(&d_capture_filter->thread,NULL,winsndds_read_thread,d_capture_filter);
		ms_mutex_lock(&d_capture_filter->thread_lock);
		ms_cond_wait(&d_capture_filter->thread_cond,&d_capture_filter->thread_lock);
		ms_mutex_unlock(&d_capture_filter->thread_lock);
	}
	else
	{
		ms_DirectSoundCreate( &d->out_guid, &d->lpDirectSound, NULL );

		if (d->lpDirectSound==NULL)
		{
			return ;
		}

		hWnd = GetDesktopWindow();
		if ((hr = IDirectSound_SetCooperativeLevel( d->lpDirectSound,
			hWnd, DSSCL_PRIORITY)) != DS_OK) //DSSCL_EXCLUSIVE)) != DS_OK)
		{
			return ;
		}

		ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC));
		primaryDesc.dwSize        = sizeof(DSBUFFERDESC);
		primaryDesc.dwFlags       = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_PRIMARYBUFFER;
		primaryDesc.dwBufferBytes = 0;
		primaryDesc.lpwfxFormat   = NULL;
		if ((hr = IDirectSound_CreateSoundBuffer( d->lpDirectSound,
			&primaryDesc, &pPrimaryBuffer, NULL)) != DS_OK)
		{
			return ;
		}

		if ((hr = IDirectSoundBuffer_SetFormat( pPrimaryBuffer, &d->wfx)) != DS_OK)
		{
			return ;
		}
		IDirectSoundBuffer_Release(pPrimaryBuffer);

		ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC));
		secondaryDesc.dwSize = sizeof(DSBUFFERDESC);
		secondaryDesc.dwFlags =  DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
		secondaryDesc.dwBufferBytes = d->framesPerDSBuffer;
		secondaryDesc.lpwfxFormat = &d->wfx;
		if ((hr = IDirectSound_CreateSoundBuffer( d->lpDirectSound,
			&secondaryDesc, &d->lpDirectSoundOutputBuffer, NULL)) != DS_OK)
		{
			return ;
		}
	}

	if ((hr = IDirectSoundBuffer_Lock( d->lpDirectSoundOutputBuffer, 0,
		d->framesPerDSBuffer,
		(LPVOID*)&pDSBuffData,
		&dwDataLen, NULL, 0, 0)) != DS_OK)
	{
		return ;
	}

	ZeroMemory(pDSBuffData, dwDataLen);
	if ((hr = IDirectSoundBuffer_Unlock( d->lpDirectSoundOutputBuffer,
		pDSBuffData, dwDataLen, NULL, 0)) != DS_OK)
	{
		return ;
	}

	hr = IDirectSoundBuffer_GetCurrentPosition( d->lpDirectSoundOutputBuffer,
		&playCursor, &outputBufferWriteOffsetBytes );
	if( hr != DS_OK )
	{
		return ;
	}

	hr = IDirectSoundBuffer_SetCurrentPosition( d->lpDirectSoundOutputBuffer, 0 );
	if( hr != DS_OK )
	{
		return ;
	}

	hr = IDirectSoundBuffer_Play( d->lpDirectSoundOutputBuffer, 0, 0, DSBPLAY_LOOPING);
	if( hr != DS_OK )
	{
		return ;
	}
	d->writeOffset=-1;

	return ;
}

static void winsndds_write_postprocess(MSFilter *f){
	WinSndDs *d=(WinSndDs*)f->data;

	if( d->lpDirectSoundOutputBuffer )
	{
		IDirectSoundBuffer_Stop( d->lpDirectSoundOutputBuffer );
		IDirectSoundBuffer_Release( d->lpDirectSoundOutputBuffer );
		d->lpDirectSoundOutputBuffer = NULL;
	}

	if( d->lpDirectSound )
	{
		IDirectSound_Release( d->lpDirectSound );
		d->lpDirectSound = NULL;
	}

	if( d->lpDirectSoundFullDuplex )
	{
		IDirectSoundFullDuplex_Release( d->lpDirectSoundFullDuplex );
		d->lpDirectSoundFullDuplex = NULL;
	}

	ms_message("Shutting down sound device (playing: %i) (input-output: %i) (notplayed: %i)", d->nbufs_playing, d->stat_input - d->stat_output, d->stat_notplayed);
	d->writeOffset=-1;
}

static void winsndds_write_process(MSFilter *f){
	WinSndDs *d=(WinSndDs*)f->data;
	int discarded=0;
	DWORD dwStatus;
	HRESULT hr;

	if (d->lpDirectSoundOutputBuffer==NULL) {
		ms_queue_flush(f->inputs[0]);
		return;
	}

	ms_bufferizer_put_from_queue(&d->output_buff,f->inputs[0]);

	if (d->writeOffset==-1)
	{
		if (ms_bufferizer_get_avail(&d->output_buff)>=d->framesPerDSBuffer)
		{
			DWORD playCursor;
			DWORD outputBufferWriteOffsetBytes;
			IDirectSoundBuffer_GetCurrentPosition( d->lpDirectSoundOutputBuffer,
				&playCursor, &outputBufferWriteOffsetBytes );
			d->writeOffset = outputBufferWriteOffsetBytes;
		}
		else
			return;
	}

	DWORD current_playOffset;
	long msize_max = 0;
	DWORD currentWriteOffset;
	IDirectSoundBuffer_GetCurrentPosition( d->lpDirectSoundOutputBuffer,
		&current_playOffset, &currentWriteOffset );

	msize_max = current_playOffset - currentWriteOffset;
	if( msize_max < 0 ) msize_max += d->framesPerDSBuffer;

	/* write from d->writeOffset up to current_playOffset */
	msize_max=current_playOffset-d->writeOffset;
	if( msize_max < 0 ) msize_max += d->framesPerDSBuffer;

	//ms_message("DS information: last_writeOffset=%i current_playOffset=%i current_writeOffset=%i max_writable=%i",
	//	d->writeOffset, current_playOffset, currentWriteOffset, msize_max);

	hr = IDirectSoundBuffer_GetStatus (d->lpDirectSoundOutputBuffer, &dwStatus);
	if (dwStatus & DSBSTATUS_BUFFERLOST) {
		hr = IDirectSoundBuffer_Restore (d->lpDirectSoundOutputBuffer);
		d->writeOffset = 0;
		ms_message("DSBSTATUS_BUFFERLOST: restoring buffer");
	}

	if (msize_max==0)
		return;
	int msize=d->framesPerDSBuffer/4;
	if (msize>msize_max)
		msize = msize_max;
	while (ms_bufferizer_get_avail(&d->output_buff)>=msize)
	{
		LPBYTE lpOutBuf1 = NULL;
		LPBYTE lpOutBuf2 = NULL;
		DWORD  dwOutSize1 = 0;
		DWORD  dwOutSize2 = 0;
		char input[15360];

		hr = IDirectSoundBuffer_Lock ( d->lpDirectSoundOutputBuffer,
			d->writeOffset, msize,
			(void **) &lpOutBuf1, &dwOutSize1,
			(void **) &lpOutBuf2, &dwOutSize2, 0); /* DSBLOCK_FROMWRITECURSOR); */
		if (hr != DS_OK)
		{
			ms_error("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n", hr);
			break;
		}

		if (dwOutSize1==0)
		{
			ms_error("no free room to play sample\n");
		}
		else if (dwOutSize1+dwOutSize2!=msize)
		{
			ms_bufferizer_read(&d->output_buff,(uint8_t*)input,dwOutSize1+dwOutSize2);
			memcpy(lpOutBuf1, input, dwOutSize1);
			memcpy(lpOutBuf2, input+dwOutSize1, dwOutSize2);
		}
		else if (dwOutSize1>=msize)
		{
			ms_bufferizer_read(&d->output_buff,(uint8_t*)input,msize);
			memcpy(lpOutBuf1, input, msize);
		}
		else
		{
			ms_bufferizer_read(&d->output_buff,(uint8_t*)input,msize);
			memcpy(lpOutBuf1, input, dwOutSize1);
			memcpy(lpOutBuf2, input+dwOutSize1, dwOutSize2);
		}

		d->writeOffset=(d->writeOffset+dwOutSize1+dwOutSize2) % d->framesPerDSBuffer;
		msize_max = msize_max - (dwOutSize1+dwOutSize2);
		if (msize>msize_max)
			msize = msize_max;
		IDirectSoundBuffer_Unlock( d->lpDirectSoundOutputBuffer,
			lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2);
		if (dwOutSize1==0)
			break;
		if (dwOutSize1+dwOutSize2!=msize)
			break;
	}
	if (msize==0)
	{
		if (ms_bufferizer_get_avail(&d->output_buff)>=3*d->wfx.nSamplesPerSec/50)
		{
			ms_warning("Removing extra data for sound card %i", ms_bufferizer_get_avail(&d->output_buff));
			ms_bufferizer_uninit(&d->output_buff);
			ms_bufferizer_init(&d->output_buff);
		}
	}

	if (discarded>0)
		ms_warning("Extra data for sound card removed (%i buf), (playing: %i) (input-output: %i)", discarded, d->nbufs_playing, d->stat_input - d->stat_output);
}

static int get_rate(MSFilter *f, void *arg){
	WinSndDs *d=(WinSndDs*)f->data;
	*((int*)arg)=d->wfx.nSamplesPerSec;
	return 0;
}

static int set_rate(MSFilter *f, void *arg){
	WinSndDs *d=(WinSndDs*)f->data;
	d->wfx.nSamplesPerSec=*((int*)arg);
	return 0;
}

static int set_nchannels(MSFilter *f, void *arg){
	WinSndDs *d=(WinSndDs*)f->data;
	d->wfx.nChannels=*((int*)arg);
	return 0;
}

static int winsndds_get_stat_input(MSFilter *f, void *arg){
	WinSndDs *d=(WinSndDs*)f->data;
	return d->stat_input;
}

static int winsndds_get_stat_ouptut(MSFilter *f, void *arg){
	WinSndDs *d=(WinSndDs*)f->data;

	return d->stat_output;
}

static int winsndds_get_stat_discarded(MSFilter *f, void *arg){
	WinSndDs *d=(WinSndDs*)f->data;

	return d->stat_notplayed;
}

static MSFilterMethod winsndds_methods[]={
	{	MS_FILTER_GET_SAMPLE_RATE	, get_rate	},
	{	MS_FILTER_SET_SAMPLE_RATE	, set_rate	},
	{	MS_FILTER_SET_NCHANNELS		, set_nchannels	},
	{	MS_FILTER_GET_STAT_INPUT, winsndds_get_stat_input },
	{	MS_FILTER_GET_STAT_OUTPUT, winsndds_get_stat_ouptut },
	{	MS_FILTER_GET_STAT_DISCARDED, winsndds_get_stat_discarded },
	{	0				, NULL		}
};

MSFilterDesc winsndds_read_desc={
	MS_WINSNDDS_READ_ID,
	"DirecSoundRead",
	"DirectSound capture filter for Windows",
	MS_FILTER_OTHER,
	NULL,
	0,
	1,
	winsndds_init,
	winsndds_read_preprocess,
	winsndds_read_process,
	winsndds_read_postprocess,
	winsndds_uninit,
	winsndds_methods
};


MSFilterDesc winsndds_write_desc={
	MS_WINSNDDS_WRITE_ID,
	"DirecSoundWrite",
	"DirectSound playback filter for Windows",
	MS_FILTER_OTHER,
	NULL,
	1,
	0,
	winsndds_init,
	winsndds_write_preprocess,
	winsndds_write_process,
	winsndds_write_postprocess,
	winsndds_uninit,
	winsndds_methods
};

MSFilter *ms_winsndds_read_new(MSSndCard *card){
	MSFilter *f=ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card), &winsndds_read_desc);
	WinSndDsCard *wc=(WinSndDsCard*)card->data;
	WinSndDs *d=(WinSndDs*)f->data;
	d->dev_id=wc->in_devid;
	memcpy(&d->in_guid, &wc->in_guid, sizeof(GUID));
	memcpy(&d->out_guid, &wc->out_guid, sizeof(GUID));
	return f;
}


MSFilter *ms_winsndds_write_new(MSSndCard *card){
	MSFilter *f=ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card), &winsndds_write_desc);
	WinSndDsCard *wc=(WinSndDsCard*)card->data;
	WinSndDs *d=(WinSndDs*)f->data;
	d->dev_id=wc->out_devid;
	memcpy(&d->in_guid, &wc->in_guid, sizeof(GUID));
	memcpy(&d->out_guid, &wc->out_guid, sizeof(GUID));
	return f;
}

MS_FILTER_DESC_EXPORT(winsndds_read_desc)
MS_FILTER_DESC_EXPORT(winsndds_write_desc)

#endif
