/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#if defined(TARGET_WIN) && defined(_MSC_VER)

#include "Audio_DirectSound.h"
#include "Audio_Helper.h"
#include "../Helper/HL_Exception.h"
#include "../Helper/HL_Log.h"

#include <assert.h>
#include <dsconf.h>
#include <process.h>
#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "dxguid.lib")

#define DRVM_MAPPER_CONSOLEVOICECOM_GET (0x2000 + 23)
#define DRVM_MAPPER_PREFERRED_GET (0x2000 + 21)

#define DRV_QUERYFUNCTIONINSTANCEID  (DRV_RESERVED + 17)
#define DRV_QUERYFUNCTIONINSTANCEIDSIZE (DRV_RESERVED + 18)

#define LOG_SUBSYSTEM "audio"

using namespace Audio;

class DSoundInit
{
public:
    DSoundInit();
    virtual ~DSoundInit();

    void load();
    void unload();

    struct EntryPoints
    {
        HINSTANCE mInstance;

        HRESULT (WINAPI *DirectSoundCreate8)(LPGUID, LPDIRECTSOUND8 *, LPUNKNOWN);
        HRESULT (WINAPI *DirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
        HRESULT (WINAPI *DirectSoundEnumerateA)(LPDSENUMCALLBACKA, LPVOID);

        HRESULT (WINAPI *DirectSoundCaptureCreate8)(LPGUID, LPDIRECTSOUNDCAPTURE8* , LPUNKNOWN);
        HRESULT (WINAPI *DirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
        HRESULT (WINAPI *DirectSoundCaptureEnumerateA)(LPDSENUMCALLBACKA, LPVOID);
        HRESULT (WINAPI *GetDeviceID)(LPCGUID src, LPGUID dst);
    } mRoutines;

protected:
    LPDIRECTSOUND mDirectSound;
    Mutex mGuard;
    unsigned int  mRefCount;
};

DSoundInit gDSoundInit;

DSoundInit::DSoundInit()
    :mRefCount(0)
{
}

DSoundInit::~DSoundInit()
{
    //Unload();
}

void DSoundInit::load()
{
    Lock l(mGuard);

    if (++mRefCount == 1)
    {
        HRESULT hr = E_FAIL;

        hr = ::CoInitialize(NULL);

        //load the DirectSound DLL
        mRoutines.mInstance = ::LoadLibraryW(L"dsound.dll");
        if (!mRoutines.mInstance)
            throw std::logic_error("Cannot load dsound.dll");

        mRoutines.DirectSoundCaptureCreate8 = (HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN))::GetProcAddress(mRoutines.mInstance, "DirectSoundCaptureCreate8");
        mRoutines.DirectSoundCaptureEnumerateW = (HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID))::GetProcAddress(mRoutines.mInstance, "DirectSoundCaptureEnumerateW");
        mRoutines.DirectSoundCreate8 = (HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUND8 *, LPUNKNOWN))::GetProcAddress(mRoutines.mInstance, "DirectSoundCreate8");
        mRoutines.DirectSoundEnumerateW = (HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID))::GetProcAddress(mRoutines.mInstance, "DirectSoundEnumerateW");
        mRoutines.GetDeviceID = (HRESULT (WINAPI*) (LPCGUID, LPGUID)) GetProcAddress(mRoutines.mInstance, "GetDeviceID");
    }
}

void DSoundInit::unload()
{
    Lock l(mGuard);
    if (--mRefCount == 0)
    {
        if (mRoutines.mInstance)
        {
            ::FreeLibrary(mRoutines.mInstance);
            mRoutines.mInstance = NULL;
        }

        CoUninitialize();
    }
}

// --------------- VistaEnumerator ---------------------
VistaEnumerator::VistaEnumerator()
    :mCollection(NULL), mDefaultDevice(NULL), mEnumerator(NULL), mDirection(eCapture)
{
}

VistaEnumerator::~VistaEnumerator()
{
    close();
}

void VistaEnumerator::open(int direction)
{
    const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
    const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);

    mDirection = (direction == myMicrophone) ? eCapture : eRender;

    HRESULT hr = CoCreateInstance(
                CLSID_MMDeviceEnumerator, NULL,
                CLSCTX_ALL, IID_IMMDeviceEnumerator,
                (void**)&mEnumerator);
    if (!mEnumerator)
        return;

    hr = mEnumerator->EnumAudioEndpoints(mDirection, DEVICE_STATE_ACTIVE, &mCollection);
    if (!mCollection)
        return;
    hr = mEnumerator->GetDefaultAudioEndpoint(mDirection, eCommunications, &mDefaultDevice);
    if (!mDefaultDevice)
        return;

    enumerate();
}

void VistaEnumerator::close()
{
    try
    {
        if (mCollection)
        {
            mCollection->Release();
            mCollection = NULL;
        }

        if (mDefaultDevice)
        {
            //mDefaultDevice->Release();
            mDefaultDevice = NULL;
        }

        if (mEnumerator)
        {
            mEnumerator->Release();
            mEnumerator = NULL;
        }
    }
    catch(...)
    {
    }
}

IMMDevice* VistaEnumerator::mapIndexToInterface(int index)
{
    if (!mCollection)
        return NULL;

    if (index == -1)
        return mDefaultDevice;

    size_t idSize = 0;
    MMRESULT mmres  = 0;
    WCHAR* id = NULL;
    if (mDirection == eCapture)
    {
        mmres = waveInMessage((HWAVEIN)index, DRV_QUERYFUNCTIONINSTANCEIDSIZE, (DWORD_PTR)&idSize, NULL);

        if (mmres != MMSYSERR_NOERROR)
            return NULL;

        id = (WCHAR*)_alloca(idSize*sizeof(WCHAR));
        mmres = waveInMessage((HWAVEIN)index, DRV_QUERYFUNCTIONINSTANCEID, (DWORD_PTR)id, idSize);
    }
    else
    {
        mmres = waveOutMessage((HWAVEOUT)index, DRV_QUERYFUNCTIONINSTANCEIDSIZE, (DWORD_PTR)&idSize, NULL);

        if (mmres != MMSYSERR_NOERROR)
            return NULL;

        id = (WCHAR*)_alloca(idSize*sizeof(WCHAR));
        mmres = waveOutMessage((HWAVEOUT)index, DRV_QUERYFUNCTIONINSTANCEID, (DWORD_PTR)id, idSize);
    }

    if (mmres != MMSYSERR_NOERROR)
        return NULL;

    IMMDevice* pDevice = NULL;
    mEnumerator->GetDevice(id, &pDevice);

    return pDevice;
}

void VistaEnumerator::enumerate()
{
    mNameList.clear();
    int res = (int)count();

    for (int i=0; i<res; i++)
    {
        IMMDevice* dev = mapIndexToInterface(i);
        if (dev)
        {
            IPropertyStore* store = NULL;
            dev->OpenPropertyStore(STGM_READ, &store);
            if (store)
            {
                PROPVARIANT varName;
                PropVariantInit(&varName);
                if (store->GetValue(PKEY_Device_FriendlyName, &varName) == S_OK)
                    mNameList.push_back(varName.pwszVal);
                PropVariantClear(&varName);
                store->Release();
            }
            dev->Release();
        }
    }
}

std::tstring VistaEnumerator::nameAt(int index)
{
    return mNameList[index];
}

int VistaEnumerator::idAt(int index)
{
    return index;
}

int VistaEnumerator::count()
{
    if (mDirection == eCapture)
        return waveInGetNumDevs();
    else
        return waveOutGetNumDevs();
}

int VistaEnumerator::indexOfDefaultDevice()
{
    DWORD devID = -1, status = 0;

    if (mDirection == mySpeaker)
    {
        if (waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&devID, (DWORD_PTR)&status) != MMSYSERR_NOERROR)
            waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&devID, (DWORD_PTR)&status);
    }
    else
    {
        if (waveInMessage((HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&devID, (DWORD_PTR)&status) != MMSYSERR_NOERROR)
            waveInMessage((HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&devID, (DWORD_PTR)&status);
    }
    return devID;
}

// -------------- XpEnumerator ---------------
XpEnumerator::XpEnumerator()
    :mDirection(-1)
{
}

XpEnumerator::~XpEnumerator()
{
}

void XpEnumerator::open(int direction)
{
    mNameList.clear();
    if (direction == myMicrophone)
    {
        int count = waveInGetNumDevs();
        for (int i=0; i<count; i++)
        {
            WAVEINCAPSW caps;
            if (waveInGetDevCapsW(i, &caps, sizeof caps) == MMSYSERR_NOERROR)
                mNameList.push_back(caps.szPname);
            else
                mNameList.push_back(L"Bad device");
        }
    }
    else
    {
        int count = waveOutGetNumDevs();
        for (int i=0; i<count; i++)
        {
            WAVEOUTCAPSW caps;
            if (waveOutGetDevCapsW(i, &caps, sizeof caps) == MMSYSERR_NOERROR)
                mNameList.push_back(caps.szPname);
            else
                mNameList.push_back(L"Bad device");
        }
    }
}

void XpEnumerator::close()
{
}

int XpEnumerator::count()
{
    return mNameList.size();
}

std::tstring XpEnumerator::nameAt(int index)
{
    return mNameList[index];
}

int XpEnumerator::idAt(int index)
{
    return index;
}

int XpEnumerator::indexOfDefaultDevice()
{
    DWORD devID = -1, status = 0;

    if (mDirection == mySpeaker)
    {
        if (waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&devID, (DWORD_PTR)&status) != MMSYSERR_NOERROR)
            waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&devID, (DWORD_PTR)&status);
    }
    else
    {
        if (waveInMessage((HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&devID, (DWORD_PTR)&status) != MMSYSERR_NOERROR)
            waveInMessage((HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&devID, (DWORD_PTR)&status);
    }
    return devID;
}

// -------- DSoundInputDevice ---------------
DSoundInputDevice::DSoundInputDevice(GUID deviceId)
    :mSimulate(false), mBufferIndex(0), mGUID(deviceId), mThreadHandle(0), mDenoiser(AUDIO_SAMPLERATE), mEnableDenoiser(true),
      mNullAudio(AUDIO_MIC_BUFFER_LENGTH, AUDIO_MIC_BUFFER_COUNT)
    #ifdef AUDIO_DUMPINPUT
    ,mDump(AUDIO_SAMPLERATE)
    #endif
{
    gDSoundInit.load();

    mSimulate = false;
    mNotifications = NULL;
    mDevice = NULL;
    mBuffer = NULL;
    mShutdownSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
    for (unsigned i=0; i<AUDIO_MIC_BUFFER_COUNT; i++)
    {
        mEventArray[i].dwOffset = (i + 1) * AUDIO_MIC_BUFFER_SIZE - 1;
        mEventSignals[i] = mEventArray[i].hEventNotify = ::CreateEvent(NULL, TRUE, FALSE, NULL);
    }
    mRefCount = 0;
}

DSoundInputDevice::~DSoundInputDevice()
{
    close();
    ::CloseHandle(mShutdownSignal);
    for (int i=0; i<AUDIO_MIC_BUFFER_COUNT; i++)
        ::CloseHandle(mEventArray[i].hEventNotify);

    gDSoundInit.unload();
}

void DSoundInputDevice::enableDenoiser(bool enable)
{
    mEnableDenoiser = enable;
}

bool DSoundInputDevice::isSimulate() const
{
    return mSimulate;
}

void DSoundInputDevice::openDevice()
{ 
    ICELogInfo(<< "Open DirectSound audio input.")
            ::CoInitialize(NULL);
    Lock l(mGuard);
    // Ensure if GUID is not null
    if (IsEqualGUID(mGUID, GUID_NULL))
    {
        setSimulate( true );
        return;
    }


#ifdef AUDIO_DUMPINPUT
    mDump.open(L"audioinput.wav");
#endif

    mNextBuffer = 0; mDevice = NULL; IUnknown* unk = NULL; mBuffer = NULL;
    DSoundHelper::checkComResult(gDSoundInit.mRoutines.DirectSoundCaptureCreate8(&mGUID, &mDevice, NULL));

    WAVEFORMATEX wfx;
    memset(&wfx, 0, sizeof(wfx));

    //wfx.cbSize = sizeof(wfx);
    wfx.nChannels = AUDIO_CHANNELS;
    wfx.nSamplesPerSec = AUDIO_SAMPLERATE;
    wfx.wBitsPerSample = 16;
    wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;;
    wfx.nAvgBytesPerSec = AUDIO_SAMPLERATE * 2 * AUDIO_CHANNELS;
    wfx.wFormatTag = WAVE_FORMAT_PCM;

    DSCBUFFERDESC dsbd;
    ZeroMemory(&dsbd, sizeof(dsbd));
    dsbd.dwSize = sizeof(DSCBUFFERDESC);
    dsbd.dwFlags = 0;//DSBCAPS_CTRLPOSITIONNOTIFY;
    dsbd.dwBufferBytes = AUDIO_MIC_BUFFER_COUNT * AUDIO_MIC_BUFFER_SIZE;
    dsbd.lpwfxFormat = &wfx;
    dsbd.dwFXCount = 0;
    dsbd.lpDSCFXDesc = NULL;

    IDirectSoundCaptureBuffer* dscb = NULL;
    DSoundHelper::checkComResult(mDevice->CreateCaptureBuffer(&dsbd, &dscb, NULL));
    DSoundHelper::checkComResult(dscb->QueryInterface(IID_IDirectSoundCaptureBuffer8, (void**)&mBuffer));
    DSoundHelper::checkComResult(dscb->QueryInterface(IID_IDirectSoundNotify, (void**)&mNotifications));
    DSoundHelper::checkComResult(mNotifications->SetNotificationPositions(AUDIO_MIC_BUFFER_COUNT, mEventArray));
    DSoundHelper::checkComResult(mBuffer->Start(DSCBSTART_LOOPING));
    dscb->Release();
    setSimulate( false );
}

bool DSoundInputDevice::open()
{
    ICELogInfo(<< "Request to DirectSound audio input");
    Lock lock(mGuard);
    mRefCount++;
    if (mRefCount == 1)
    {
        ICELogInfo(<< "Schedule DirectSound audio input thread");
        mThreadHandle = (HANDLE)_beginthread(&threadProc, 0, this);
    }
    return true;
}

void DSoundInputDevice::closeDevice()
{
    ICELogInfo(<<"Close DirectSound audio input");
    Lock l(mGuard);

#ifdef AUDIO_DUMPINPUT
    mDump.close();
#endif

    if (mBuffer)
    {
        mBuffer->Stop();
        mBuffer->Release();
        mBuffer = NULL;
    }
    if (mNotifications)
    {
        mNotifications->Release();
        mNotifications = NULL;
    }
    if (mDevice)
    {
        mDevice->Release();
        mDevice = NULL;
    }
    else
        return;

    ::CoUninitialize();
}

void DSoundInputDevice::close()
{
    {
        Lock l(mGuard);
        mRefCount--;
        if (mRefCount != 0)
            return;

        // Set shutdown signal
        if (!mThreadHandle)
            return;

        ::SetEvent(mShutdownSignal);
    }

    ::WaitForSingleObject(mThreadHandle, INFINITE);
    mThreadHandle = 0;
}

bool DSoundInputDevice::tryReadBuffer(void* buffer)
{
    // Ensure device exists
    if (!mDevice)
    {
        setSimulate( true );
        return false;
    }

    if (mQueue.size() >= AUDIO_MIC_BUFFER_SIZE)
    {
        memcpy(buffer, mQueue.data(), AUDIO_MIC_BUFFER_SIZE);
        if (mEnableDenoiser && AUDIO_CHANNELS == 1)
            mDenoiser.fromMic(buffer, AUDIO_MIC_BUFFER_LENGTH);

#ifdef AUDIO_DUMPINPUT
        mDump.write(buffer, AUDIO_MIC_BUFFER_SIZE);
#endif
        mQueue.erase(0, AUDIO_MIC_BUFFER_SIZE);
        return true;
    }

    try
    {
        if (::WaitForSingleObject(mEventArray[mNextBuffer].hEventNotify, AUDIO_MIC_BUFFER_COUNT * AUDIO_MIC_BUFFER_LENGTH * 4) != WAIT_OBJECT_0)
        {
            setSimulate( true );
            return false;
        }

        // See if all other buffers are signaled
        if (::WaitForMultipleObjects(AUDIO_MIC_BUFFER_COUNT, mEventSignals, TRUE, 0) != WAIT_TIMEOUT)
        {
            // Possible overflow. Consider current buffer resulting. Reset ALL events.
            for (int i = 0; i<AUDIO_MIC_BUFFER_COUNT; i++)
                ResetEvent(mEventArray[i].hEventNotify);
        }
        else
            ResetEvent(mEventArray[mNextBuffer].hEventNotify);

        // Find the buffer start offset
        mReadOffset = mNextBuffer * AUDIO_MIC_BUFFER_SIZE;

        //increase the buffer's index
        if (++mNextBuffer == AUDIO_MIC_BUFFER_COUNT)
            mNextBuffer = 0;

        LPVOID ptr1 = NULL, ptr2 = NULL; DWORD len1 = 0, len2 = 0;
        DSoundHelper::checkComResult(mBuffer->Lock(mReadOffset, AUDIO_MIC_BUFFER_SIZE, &ptr1, &len1, &ptr2, &len2, 0));

        // Copy&Enqueue captured data to mQueue
        if (ptr1 && len1)
            mQueue.appendBuffer(ptr1, len1);

        if (ptr2 && len2)
            mQueue.appendBuffer(ptr2, len2);

        DSoundHelper::checkComResult(mBuffer->Unlock(ptr1, len1, ptr2, len2));
        if (mQueue.size() >= AUDIO_MIC_BUFFER_SIZE)
        {
            memcpy(buffer, mQueue.data(), AUDIO_MIC_BUFFER_SIZE);
            if (mEnableDenoiser && AUDIO_CHANNELS == 1)
                mDenoiser.fromMic(buffer, AUDIO_MIC_BUFFER_LENGTH);

#ifdef AUDIO_DUMPINPUT
            mDump.write(buffer, AUDIO_MIC_BUFFER_SIZE);
#endif
            mQueue.erase(0, AUDIO_MIC_BUFFER_SIZE);
        }
        else
            return false;

        return true;
    }
    catch(...)
    {
        setSimulate( true );
    }
    return false;
}

void DSoundInputDevice::setSimulate(bool s)
{
    if (!mSimulate && s)
        mNullAudio.start();
    else
        if (mSimulate && !s)
            mNullAudio.stop();

    mSimulate = s;
}

Format DSoundInputDevice::getFormat()
{
    return Format();
}

int DSoundInputDevice::readBuffer(void* buffer)
{
    //Lock lock(mGuard);
    if (mRefCount <= 0 || isSimulate())
        return 0;

    // Check for finished buffer
    if (!tryReadBuffer(buffer))
        return 0;

    return AUDIO_MIC_BUFFER_SIZE;
}

void DSoundInputDevice::threadProc(void* arg)
{
    DSoundInputDevice* impl = (DSoundInputDevice*)arg;

    impl->openDevice();

    while (true)
    {
        // Poll for shutdown signal
        if (::WaitForSingleObject(impl->mShutdownSignal, 0) == WAIT_OBJECT_0)
            break;

        // Preset buffer with silence
        memset(impl->mTempBuffer, 0, AUDIO_MIC_BUFFER_SIZE);

        // Try to read buffer
        if (!impl->readBuffer(impl->mTempBuffer))
        {
            // Introduce delay here to simulate true audio
            impl->mNullAudio.waitForBuffer();
        }

        // Distribute the captured buffer
        if (impl->connection())
            impl->connection()->onMicData(impl->getFormat(), impl->mTempBuffer, AUDIO_MIC_BUFFER_SIZE);
    }

    impl->closeDevice();
}


DSoundOutputDevice::DSoundOutputDevice(GUID deviceId)
    :mDevice(NULL), mPrimaryBuffer(NULL), mBuffer(NULL),
      mWriteOffset(0), mPlayedSamples(0), mTotalPlayed(0), mTail(0),
      mThreadHandle(0), mSimulate(false), mGUID(deviceId),
      mNullAudio(AUDIO_SPK_BUFFER_LENGTH, AUDIO_SPK_BUFFER_COUNT)
{
    gDSoundInit.load();
    mShutdownSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
    mBufferSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
    mRefCount = 0;
}


DSoundOutputDevice::~DSoundOutputDevice()
{
    close();

    // Destroy used signals
    ::CloseHandle(mShutdownSignal);
    ::CloseHandle(mBufferSignal);
    gDSoundInit.unload();
}

bool DSoundOutputDevice::open()
{
    ICELogInfo(<< "Request to DirectSound audio output");
    // Start thread
    mRefCount++;
    if (mRefCount == 1)
    {
        ICELogInfo(<< "Schedule DirectSound audio output thread");
        mThreadHandle = (HANDLE)_beginthread(&threadProc, 0, this);
        ::SetThreadPriority(mThreadHandle, THREAD_PRIORITY_TIME_CRITICAL);
    }
    return true;
}

void DSoundOutputDevice::close()
{
    if (mRefCount == 0)
        return;
    mRefCount--;
    if (mRefCount > 0)
        return;

    // Tell the thread to exit
    SetEvent(mShutdownSignal);

    // Wait for thread
    if (mThreadHandle)
        WaitForSingleObject(mThreadHandle, INFINITE);
    mThreadHandle = 0;
}

void DSoundOutputDevice::openDevice()
{
    ICELogInfo(<< "Open DirectSound audio output");
    if (IsEqualGUID(mGUID, GUID_NULL))
    {
        setSimulate( true );
        return;
    }

    mWriteOffset = 0;
    mPlayedSamples = 0;
    mSentBytes = 0;
    mPlayCursor = 0;
    mBufferSize = AUDIO_SPK_BUFFER_COUNT * AUDIO_SPK_BUFFER_SIZE;

    DSoundHelper::checkComResult(gDSoundInit.mRoutines.DirectSoundCreate8(&mGUID, &mDevice, NULL));
    DSoundHelper::checkComResult(mDevice->SetCooperativeLevel(::GetDesktopWindow(), DSSCL_PRIORITY));

    WAVEFORMATEX wfx;
    memset(&wfx, 0, sizeof(wfx));

    wfx.cbSize = sizeof(wfx);
    wfx.nChannels = AUDIO_CHANNELS;
    wfx.nSamplesPerSec = AUDIO_SAMPLERATE;
    wfx.wBitsPerSample = 16;
    wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
    wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
    wfx.wFormatTag = WAVE_FORMAT_PCM;

    DSBUFFERDESC dsbd;
    ZeroMemory(&dsbd, sizeof(dsbd));
    dsbd.dwSize = sizeof(DSBUFFERDESC);
    dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
    dsbd.dwBufferBytes = 0;
    dsbd.lpwfxFormat = NULL;//&wfx;
    dsbd.guid3DAlgorithm = DS3DALG_DEFAULT;

    DSoundHelper::checkComResult(mDevice->CreateSoundBuffer(&dsbd, &mPrimaryBuffer, NULL ));
    DSBCAPS caps;
    caps.dwSize = sizeof(caps);
    caps.dwFlags = 0;
    caps.dwBufferBytes = 0;
    caps.dwPlayCpuOverhead = 0;
    caps.dwUnlockTransferRate = 0;

    DSoundHelper::checkComResult(mPrimaryBuffer->GetCaps(&caps));

    dsbd.dwSize = sizeof(caps);
    dsbd.dwFlags = DSBCAPS_GLOBALFOCUS;
    dsbd.lpwfxFormat = &wfx;
    dsbd.guid3DAlgorithm = DS3DALG_DEFAULT;
    dsbd.dwBufferBytes = mBufferSize;

    DSoundHelper::checkComResult(mDevice->CreateSoundBuffer(&dsbd, &mBuffer, NULL));

    // Fill the buffer with silence
    LPVOID ptr1 = NULL, ptr2 = NULL; DWORD len1 = 0, len2 = 0;
    DSoundHelper::checkComResult(mBuffer->Lock(0, AUDIO_SPK_BUFFER_SIZE * AUDIO_SPK_BUFFER_COUNT, &ptr1, &len1, &ptr2, &len2, 0));
    if (len1 && ptr1)
        memset(ptr1, 0, len1);
    if (len2 && ptr2)
        memset(ptr2, 0, len2);
    DSoundHelper::checkComResult(mBuffer->Unlock(ptr1, len1, ptr2, len2));
    DSoundHelper::checkComResult(mBuffer->Play(0,0,DSBPLAY_LOOPING));
    mBuffer->GetCurrentPosition(NULL, &mWriteCursor);
}

void DSoundOutputDevice::closeDevice()
{
    if (mBuffer)
    {
        mBuffer->Stop();
        mBuffer->Release();
        mBuffer = NULL;
    }

    if (mPrimaryBuffer)
    {
        mPrimaryBuffer->Stop();
        mPrimaryBuffer->Release();
        mPrimaryBuffer = NULL;
    }

    if (mDevice)
    {
        mDevice->Release();
        mDevice = NULL;
    }
}

void DSoundOutputDevice::restoreBuffer()
{
    if (mSimulate)
        return;

    DWORD status = 0;
    DSoundHelper::checkComResult(mBuffer->GetStatus(&status));
    if (DSBSTATUS_BUFFERLOST == status)
        DSoundHelper::checkComResult(mBuffer->Restore());
}

bool DSoundOutputDevice::getMediaFrame()
{
    try
    {
        memset(mMediaFrame, 0, sizeof mMediaFrame);
        if (mConnection)
            mConnection->onSpkData(getFormat(), mMediaFrame, sizeof mMediaFrame);
    }
    catch(...)
    {}

    return true;
}

bool DSoundOutputDevice::process()
{
    if (mSimulate)
        return false;

    // Find amount of written data from last call
    DWORD cursor = 0;
    DSoundHelper::checkComResult(mBuffer->GetCurrentPosition(NULL, &cursor));
    unsigned written;
    if (cursor < mWriteCursor)
        written = mBufferSize - mWriteCursor + cursor;
    else
        written = cursor - mWriteCursor;

    mWriteCursor += (written / AUDIO_SPK_BUFFER_SIZE) * AUDIO_SPK_BUFFER_SIZE;
    mWriteCursor %= mBufferSize;
    bool finished = false;
    for (unsigned frameIndex = 0; frameIndex < written / AUDIO_SPK_BUFFER_SIZE && !finished; frameIndex++)
    {
        unsigned offset = mWriteOffset + frameIndex * AUDIO_SPK_BUFFER_SIZE;
        offset %= mBufferSize;

        // See what we can write
        LPVOID ptr1 = NULL, ptr2 = NULL; DWORD len1 = 0, len2 = 0;
        DSoundHelper::checkComResult(mBuffer->Lock(offset, AUDIO_SPK_BUFFER_SIZE, &ptr1, &len1, &ptr2, &len2, 0));

        assert(ptr2 == NULL);
        assert(len1 >= AUDIO_SPK_BUFFER_SIZE);

        if (getMediaFrame())
            finished = true;
        memmove(ptr1, mMediaFrame, AUDIO_SPK_BUFFER_SIZE);
        DSoundHelper::checkComResult(mBuffer->Unlock(ptr1, AUDIO_SPK_BUFFER_SIZE, ptr2, 0));
    }

    // Increase write offset
    mWriteOffset += (written / AUDIO_SPK_BUFFER_SIZE) * AUDIO_SPK_BUFFER_SIZE;
    mWriteOffset %= mBufferSize;

    return true;
}


void DSoundOutputDevice::threadProc(void* arg)
{
    DSoundOutputDevice* impl = (DSoundOutputDevice*)arg;
    impl->openDevice();

    DWORD waitResult = 0;
    HANDLE waitArray[2] = {impl->mBufferSignal, impl->mShutdownSignal};
    unsigned exitCount = 0;
    bool exitSignal = false;
    while (true)
    {
        // Poll for shutdown signal
        if (WAIT_OBJECT_0 == ::WaitForSingleObject(impl->mShutdownSignal, 0))
            break;

        if (impl->isSimulate())
        {
            impl->mNullAudio.waitForBuffer();
            impl->getMediaFrame();
        }
        else
        {
            // Poll events
            waitResult = ::WaitForMultipleObjects(2, waitArray, FALSE, 5);
            if (waitResult == WAIT_OBJECT_0 + 1)
                break;
            try
            {
                impl->restoreBuffer();
                impl->process();
            }
            catch(const Exception& e)
            {
                ICELogError(<< "DirectSound output failed with code = " << e.code() << ", subcode = " << e.subcode());
                impl->setSimulate(true);
            }
            catch(...)
            {
                ICELogError(<< "DirectSound output failed due to unexpected exception.");
                impl->setSimulate(true);
            }
        }
    }
    impl->closeDevice();
}

unsigned DSoundOutputDevice::playedTime() const
{
    return 0;
}


void DSoundOutputDevice::setSimulate(bool s)
{
    mSimulate = s;
}

bool DSoundOutputDevice::isSimulate() const
{
    return mSimulate;
}

Format DSoundOutputDevice::getFormat()
{
    return Format();
}

bool DSoundOutputDevice::closing()
{
    return false;
}

typedef WINUSERAPI HRESULT (WINAPI *LPFNDLLGETCLASSOBJECT) (const CLSID &, const IID &, void **);

HRESULT DirectSoundPrivateCreate (OUT LPKSPROPERTYSET * ppKsPropertySet) 
{ 
    HMODULE                 hLibDsound              = NULL;
    LPFNDLLGETCLASSOBJECT   pfnDllGetClassObject    = NULL;
    LPCLASSFACTORY          pClassFactory           = NULL;
    LPKSPROPERTYSET         pKsPropertySet          = NULL;
    HRESULT                 hr                      = DS_OK;

    // Load dsound.dll
    hLibDsound = LoadLibrary(TEXT("dsound.dll"));

    if(!hLibDsound)
    {
        hr = DSERR_GENERIC;
    }

    // Find DllGetClassObject
    if(SUCCEEDED(hr))
    {
        pfnDllGetClassObject =
                (LPFNDLLGETCLASSOBJECT)GetProcAddress ( hLibDsound, "DllGetClassObject" );


        if(!pfnDllGetClassObject)
        {
            hr = DSERR_GENERIC;
        }
    }

    // Create a class factory object
    if(SUCCEEDED(hr))
    {
        hr = pfnDllGetClassObject (CLSID_DirectSoundPrivate, IID_IClassFactory, (LPVOID *)&pClassFactory );
    }

    // Create the DirectSoundPrivate object and query for an IKsPropertySet
    // interface
    if(SUCCEEDED(hr))
    {
        hr = pClassFactory->CreateInstance ( NULL, IID_IKsPropertySet, (LPVOID *)&pKsPropertySet );
    }

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

    // Handle final success or failure
    if(SUCCEEDED(hr))
    {
        *ppKsPropertySet = pKsPropertySet;
    }
    else if(pKsPropertySet)
    {
        pKsPropertySet->Release();
    }

    FreeLibrary(hLibDsound);

    return hr;
} 

BOOL GetInfoFromDSoundGUID( GUID i_sGUID, int &dwWaveID)
{ 
    LPKSPROPERTYSET         pKsPropertySet = NULL;
    HRESULT                 hr;
    BOOL					retval = FALSE;

    PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA psDirectSoundDeviceDescription = NULL;
    DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA sDirectSoundDeviceDescription;

    memset(&sDirectSoundDeviceDescription,0,sizeof(sDirectSoundDeviceDescription));
    hr = DirectSoundPrivateCreate( &pKsPropertySet );
    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;
            /*Description = psDirectSoundDeviceDescription->Description;
      Module = psDirectSoundDeviceDescription->Module;
      Interface = psDirectSoundDeviceDescription->Interface;*/
            delete [] psDirectSoundDeviceDescription;
            retval = TRUE;
        }

        pKsPropertySet->Release();
    }

    return retval;
} 

struct EnumResult
{
    int mDeviceId;
    GUID mGuid;
};

BOOL CALLBACK DSEnumCallback(
        LPGUID  lpGuid,
        LPCTSTR  lpcstrDescription,
        LPCTSTR  lpcstrModule,
        LPVOID  lpContext
        )
{
    if (lpGuid)
    {

        int devId = -1;
        GetInfoFromDSoundGUID(*lpGuid, devId);
        EnumResult* er = (EnumResult*)lpContext;
        if (er->mDeviceId == devId)
        {
            er->mGuid = *lpGuid;
            return FALSE;
        }
        else
            return TRUE;
    }
    else
        return TRUE;
}


GUID DSoundHelper::deviceId2Guid(int deviceId, bool captureDevice)
{
    EnumResult er;
    er.mDeviceId = deviceId;
    er.mGuid = GUID_NULL;
    memset(&er.mGuid, 0, sizeof er.mGuid);
    if (captureDevice)
        DirectSoundCaptureEnumerate(DSEnumCallback, &er);
    else
        DirectSoundEnumerate(DSEnumCallback, &er);

    return er.mGuid;
}

void DSoundHelper::checkComResult(HRESULT code)
{
    if (FAILED(code))
        throw Exception(ERR_DSOUND);
}

#endif
