rtphone/src/engine/audio/Audio_DirectSound.cpp

1103 lines
29 KiB
C++

/* 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 "DirectSound"
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