555 lines
14 KiB
C++
555 lines
14 KiB
C++
/* Copyright(C) 2007-2014 VoIP objects (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/. */
|
|
|
|
#ifdef TARGET_WIN
|
|
|
|
#include "Audio_Wmme.h"
|
|
#include "Audio_Helper.h"
|
|
#include "../Helper/HL_Exception.h"
|
|
|
|
#include <process.h>
|
|
using namespace Audio;
|
|
|
|
|
|
WmmeInputDevice::Buffer::Buffer()
|
|
{
|
|
// Do not use WAVEHDR allocated on stack!
|
|
mHeaderHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof WAVEHDR);
|
|
if (!mHeaderHandle)
|
|
throw Exception(ERR_WMME_FAILED, GetLastError());
|
|
mHeader = (WAVEHDR*)GlobalLock(mHeaderHandle);
|
|
|
|
mDataHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, AUDIO_MIC_BUFFER_SIZE);
|
|
if (!mDataHandle)
|
|
throw Exception(ERR_WMME_FAILED, GetLastError());
|
|
mData = GlobalLock(mDataHandle);
|
|
|
|
memset(mHeader, 0, sizeof *mHeader);
|
|
mHeader->dwBufferLength = AUDIO_MIC_BUFFER_SIZE;
|
|
mHeader->dwFlags = 0;
|
|
mHeader->lpData = (LPSTR)mData;
|
|
}
|
|
|
|
WmmeInputDevice::Buffer::~Buffer()
|
|
{
|
|
if (mDataHandle)
|
|
{
|
|
GlobalUnlock(mDataHandle);
|
|
GlobalFree(mDataHandle);
|
|
}
|
|
if (mHeaderHandle)
|
|
{
|
|
GlobalUnlock(mHeaderHandle);
|
|
GlobalFree(mHeaderHandle);
|
|
}
|
|
}
|
|
|
|
bool WmmeInputDevice::Buffer::prepare(HWAVEIN device)
|
|
{
|
|
MMRESULT resCode = MMSYSERR_NOERROR;
|
|
mHeader->dwFlags = 0;
|
|
mHeader->dwBufferLength = AUDIO_MIC_BUFFER_SIZE;
|
|
mHeader->lpData = (LPSTR)mData;
|
|
|
|
resCode = waveInPrepareHeader(device, mHeader, sizeof *mHeader);
|
|
// if (resCode != MMSYSERR_NOERROR)
|
|
// LogCritical("Audio", << "Failed to prepare source header. Error code " << resCode << ".");
|
|
|
|
return resCode == MMSYSERR_NOERROR;
|
|
}
|
|
|
|
bool WmmeInputDevice::Buffer::unprepare(HWAVEIN device)
|
|
{
|
|
if (mHeader->dwFlags & WHDR_PREPARED)
|
|
{
|
|
MMRESULT resCode = waveInUnprepareHeader(device, mHeader, sizeof *mHeader);
|
|
// if (resCode != MMSYSERR_NOERROR)
|
|
// LogCritical("Audio", << "Failed to unprepare source header. Error code " << resCode << ".");
|
|
return resCode == MMSYSERR_NOERROR;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WmmeInputDevice::Buffer::isFinished()
|
|
{
|
|
return (mHeader->dwFlags & WHDR_DONE) != 0;
|
|
}
|
|
|
|
bool WmmeInputDevice::Buffer::addToDevice(HWAVEIN device)
|
|
{
|
|
MMRESULT resCode = waveInAddBuffer(device, mHeader, sizeof(*mHeader));
|
|
// if (resCode != MMSYSERR_NOERROR)
|
|
// LogCritical("Audio", << "Failed to add buffer to source audio device. Error code is " << resCode << ".");
|
|
return resCode == MMSYSERR_NOERROR;
|
|
}
|
|
|
|
void* WmmeInputDevice::Buffer::data()
|
|
{
|
|
return mData;
|
|
}
|
|
|
|
|
|
WmmeInputDevice::WmmeInputDevice(int deviceId)
|
|
: mDevHandle(NULL), mDoneSignal(INVALID_HANDLE_VALUE), mFakeMode(false), mBufferIndex(0), mDeviceIndex(deviceId),
|
|
mThreadHandle(0)
|
|
{
|
|
mDoneSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
mShutdownSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
mRefCount = 0;
|
|
}
|
|
|
|
WmmeInputDevice::~WmmeInputDevice()
|
|
{
|
|
close();
|
|
::CloseHandle(mDoneSignal);
|
|
::CloseHandle(mShutdownSignal);
|
|
}
|
|
|
|
bool WmmeInputDevice::fakeMode()
|
|
{
|
|
return mFakeMode;
|
|
}
|
|
|
|
void CALLBACK WmmeInputDevice::callbackProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1,
|
|
DWORD_PTR dwParam2)
|
|
{
|
|
WmmeInputDevice* impl;
|
|
switch (uMsg)
|
|
{
|
|
case WIM_DATA:
|
|
impl = (WmmeInputDevice*)dwInstance;
|
|
SetEvent(impl->mDoneSignal);
|
|
break;
|
|
|
|
case WIM_CLOSE:
|
|
break;
|
|
|
|
case WIM_OPEN:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void WmmeInputDevice::openDevice()
|
|
{
|
|
// Build WAVEFORMATEX structure
|
|
WAVEFORMATEX wfx;
|
|
memset(&wfx, 0, sizeof(wfx));
|
|
|
|
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.nChannels = AUDIO_CHANNELS;
|
|
wfx.nSamplesPerSec = AUDIO_SAMPLERATE;
|
|
wfx.wBitsPerSample = 16;
|
|
wfx.cbSize = 0;
|
|
wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8;
|
|
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
|
|
|
|
// Open wavein
|
|
|
|
MMRESULT mmres =
|
|
waveInOpen(&mDevHandle, mDeviceIndex, &wfx, (DWORD_PTR)callbackProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
|
|
if (mmres != MMSYSERR_NOERROR)
|
|
{
|
|
mFakeMode = true;
|
|
return;
|
|
}
|
|
else
|
|
mFakeMode = false;
|
|
|
|
// Create the buffers for running
|
|
mBufferIndex = 0;
|
|
for (int i = 0; i < AUDIO_MIC_BUFFER_COUNT; i++)
|
|
mBufferList[i].prepare(mDevHandle);
|
|
|
|
for (int i = 0; i < AUDIO_MIC_BUFFER_COUNT; i++)
|
|
mBufferList[i].addToDevice(mDevHandle);
|
|
|
|
/*mmres = */ waveInStart(mDevHandle);
|
|
}
|
|
|
|
bool WmmeInputDevice::open()
|
|
{
|
|
Lock lock(mGuard);
|
|
|
|
mRefCount++;
|
|
if (mRefCount > 1)
|
|
return true;
|
|
|
|
mThreadHandle = (HANDLE)_beginthread(&threadProc, 0, this);
|
|
return true;
|
|
}
|
|
|
|
void WmmeInputDevice::closeDevice()
|
|
{
|
|
// Stop device
|
|
if (mDevHandle)
|
|
{
|
|
MMRESULT mmres = MMSYSERR_NOERROR;
|
|
waveInReset(mDevHandle);
|
|
waveInStop(mDevHandle);
|
|
}
|
|
|
|
// Close buffers
|
|
for (int i = 0; i < AUDIO_MIC_BUFFER_COUNT; i++)
|
|
mBufferList[i].unprepare(mDevHandle);
|
|
|
|
// Close device
|
|
if (mDevHandle)
|
|
{
|
|
waveInClose(mDevHandle);
|
|
mDevHandle = NULL;
|
|
}
|
|
}
|
|
|
|
void WmmeInputDevice::close()
|
|
{
|
|
Lock l(mGuard);
|
|
|
|
mRefCount--;
|
|
if (mRefCount != 0)
|
|
return;
|
|
|
|
// Set shutdown signal
|
|
if (!mThreadHandle)
|
|
return;
|
|
|
|
::SetEvent(mShutdownSignal);
|
|
::WaitForSingleObject(mThreadHandle, INFINITE);
|
|
mThreadHandle = 0;
|
|
}
|
|
|
|
bool WmmeInputDevice::tryReadBuffer(void* buffer)
|
|
{
|
|
Buffer& devBuffer = mBufferList[mBufferIndex];
|
|
|
|
if (!devBuffer.isFinished())
|
|
return false;
|
|
memcpy(buffer, devBuffer.data(), AUDIO_MIC_BUFFER_SIZE);
|
|
devBuffer.unprepare(mDevHandle);
|
|
devBuffer.prepare(mDevHandle);
|
|
if (!devBuffer.addToDevice(mDevHandle))
|
|
setFakeMode(true);
|
|
else
|
|
{
|
|
}
|
|
mBufferIndex = (mBufferIndex + 1) % AUDIO_MIC_BUFFER_COUNT;
|
|
return true;
|
|
}
|
|
|
|
void WmmeInputDevice::setFakeMode(bool fakeMode)
|
|
{
|
|
mFakeMode = fakeMode;
|
|
}
|
|
|
|
int WmmeInputDevice::readBuffer(void* buffer)
|
|
{
|
|
// Lock lock(mGuard);
|
|
|
|
if (mRefCount <= 0 || mFakeMode)
|
|
return 0;
|
|
|
|
// Check for finished buffer
|
|
while (!tryReadBuffer(buffer))
|
|
WaitForSingleObject(mDoneSignal, 50);
|
|
|
|
return AUDIO_MIC_BUFFER_SIZE;
|
|
}
|
|
|
|
HWAVEIN WmmeInputDevice::handle()
|
|
{
|
|
Lock lock(mGuard);
|
|
return mDevHandle;
|
|
}
|
|
|
|
void WmmeInputDevice::threadProc(void* arg)
|
|
{
|
|
WmmeInputDevice* impl = (WmmeInputDevice*)arg;
|
|
impl->openDevice();
|
|
void* buffer = _alloca(AUDIO_MIC_BUFFER_SIZE);
|
|
|
|
DWORD waitResult = 0;
|
|
HANDLE waitArray[2] = {impl->mDoneSignal, impl->mShutdownSignal};
|
|
DWORD wr;
|
|
do
|
|
{
|
|
wr = ::WaitForMultipleObjects(2, waitArray, FALSE, INFINITE);
|
|
|
|
if (wr == WAIT_OBJECT_0)
|
|
{
|
|
impl->readBuffer(buffer);
|
|
if (impl->connection())
|
|
impl->connection()->onMicData(Format(), buffer, AUDIO_MIC_BUFFER_SIZE);
|
|
}
|
|
} while (wr == WAIT_OBJECT_0);
|
|
|
|
impl->closeDevice();
|
|
}
|
|
|
|
// --- WmmeOutputDevice ---
|
|
WmmeOutputDevice::Buffer::Buffer() : mHeaderHandle(NULL), mDataHandle(NULL), mData(NULL), mHeader(NULL)
|
|
{
|
|
mHeaderHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, AUDIO_SPK_BUFFER_SIZE);
|
|
if (!mHeaderHandle)
|
|
throw Exception(ERR_NOMEM);
|
|
|
|
mDataHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, AUDIO_SPK_BUFFER_SIZE);
|
|
if (!mDataHandle)
|
|
throw Exception(ERR_NOMEM);
|
|
|
|
mHeader = (WAVEHDR*)GlobalLock(mHeaderHandle);
|
|
mData = GlobalLock(mDataHandle);
|
|
memset(mHeader, 0, sizeof *mHeader);
|
|
mHeader->dwBufferLength = AUDIO_SPK_BUFFER_SIZE;
|
|
mHeader->lpData = (LPSTR)mData;
|
|
}
|
|
|
|
WmmeOutputDevice::Buffer::~Buffer()
|
|
{
|
|
if (mHeaderHandle)
|
|
{
|
|
GlobalUnlock(mHeaderHandle);
|
|
GlobalFree(mHeaderHandle);
|
|
}
|
|
if (mDataHandle)
|
|
{
|
|
GlobalUnlock(mDataHandle);
|
|
GlobalFree(mDataHandle);
|
|
}
|
|
}
|
|
|
|
bool WmmeOutputDevice::Buffer::prepare(HWAVEOUT device)
|
|
{
|
|
MMRESULT result;
|
|
result = ::waveOutPrepareHeader(device, mHeader, sizeof *mHeader);
|
|
return result == MMSYSERR_NOERROR;
|
|
}
|
|
|
|
bool WmmeOutputDevice::Buffer::unprepare(HWAVEOUT device)
|
|
{
|
|
MMRESULT result;
|
|
result = ::waveOutUnprepareHeader(device, mHeader, sizeof *mHeader);
|
|
return result == MMSYSERR_NOERROR;
|
|
}
|
|
|
|
bool WmmeOutputDevice::Buffer::write(HWAVEOUT device)
|
|
{
|
|
MMRESULT result;
|
|
result = ::waveOutWrite(device, mHeader, sizeof *mHeader);
|
|
return result == MMSYSERR_NOERROR;
|
|
}
|
|
|
|
WmmeOutputDevice::WmmeOutputDevice(int index)
|
|
: mDevice(NULL), mDeviceIndex(index), mPlayedTime(0), mPlayedCount(0), mBufferIndex(0), mThreadHandle(NULL),
|
|
mFailed(false), mShutdownMarker(false)
|
|
{
|
|
mDoneSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
mShutdownSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
}
|
|
|
|
|
|
WmmeOutputDevice::~WmmeOutputDevice()
|
|
{
|
|
close();
|
|
|
|
// Destroy used signals
|
|
CloseHandle(mDoneSignal);
|
|
CloseHandle(mShutdownSignal);
|
|
}
|
|
|
|
bool WmmeOutputDevice::open()
|
|
{
|
|
// Start thread
|
|
mThreadHandle = (HANDLE)_beginthread(&threadProc, 0, this);
|
|
return true;
|
|
}
|
|
|
|
void WmmeOutputDevice::close()
|
|
{
|
|
// Tell the thread to exit
|
|
SetEvent(mShutdownSignal);
|
|
mShutdownMarker = true;
|
|
|
|
// Wait for thread
|
|
if (mThreadHandle)
|
|
WaitForSingleObject(mThreadHandle, INFINITE);
|
|
mThreadHandle = 0;
|
|
}
|
|
|
|
void WmmeOutputDevice::openDevice()
|
|
{
|
|
mClosing = false;
|
|
MMRESULT mmres = 0;
|
|
WAVEFORMATEX wfx;
|
|
memset(&wfx, 0, sizeof(wfx));
|
|
wfx.wFormatTag = 0x0001;
|
|
wfx.nChannels = AUDIO_CHANNELS;
|
|
wfx.nSamplesPerSec = AUDIO_SAMPLERATE;
|
|
wfx.wBitsPerSample = 16;
|
|
wfx.cbSize = 0;
|
|
wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8;
|
|
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
|
|
|
|
mmres = waveOutOpen(&mDevice, mDeviceIndex, &wfx, (DWORD_PTR)&callbackProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
|
|
if (mmres != MMSYSERR_NOERROR)
|
|
throw Exception(ERR_WMME_FAILED, mmres);
|
|
|
|
// Prebuffer silence
|
|
for (unsigned i = 0; i < AUDIO_SPK_BUFFER_COUNT; i++)
|
|
{
|
|
// bool dumb = false;
|
|
// mCallback(mBufferList[i].mData, SPK_BUFFER_SIZE, dumb, dumb);
|
|
memset(mBufferList[i].mData, 0, AUDIO_SPK_BUFFER_SIZE);
|
|
mBufferList[i].prepare(mDevice);
|
|
mBufferList[i].write(mDevice);
|
|
}
|
|
}
|
|
|
|
void WmmeOutputDevice::closeDevice()
|
|
{
|
|
Lock l(mGuard);
|
|
|
|
mClosing = true;
|
|
bool finished = false;
|
|
while (!finished)
|
|
{
|
|
WaitForSingleObject(mDoneSignal, 10);
|
|
finished = areBuffersFinished();
|
|
}
|
|
|
|
if (mDevice)
|
|
{
|
|
waveOutReset(mDevice);
|
|
waveOutClose(mDevice);
|
|
}
|
|
|
|
mDevice = NULL;
|
|
}
|
|
|
|
bool WmmeOutputDevice::areBuffersFinished()
|
|
{
|
|
Lock l(mGuard);
|
|
bool result = true;
|
|
for (unsigned i = 0; i < AUDIO_SPK_BUFFER_COUNT && result; i++)
|
|
{
|
|
bool finished = mBufferList[i].mHeader->dwFlags & WHDR_DONE || !mBufferList[i].mHeader->dwFlags;
|
|
if (finished)
|
|
{
|
|
/* if (mBufferList[i].mHeader->dwFlags & WHDR_PREPARED)
|
|
mBufferList[i].Unprepare(mDevice); */
|
|
}
|
|
result &= finished;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void WmmeOutputDevice::threadProc(void* arg)
|
|
{
|
|
WmmeOutputDevice* impl = (WmmeOutputDevice*)arg;
|
|
impl->openDevice();
|
|
|
|
DWORD waitResult = 0;
|
|
HANDLE waitArray[2] = {impl->mDoneSignal, impl->mShutdownSignal};
|
|
unsigned index, i;
|
|
unsigned exitCount = 0;
|
|
bool exitSignal = false;
|
|
do
|
|
{
|
|
// Poll for exit signal
|
|
if (!exitSignal)
|
|
exitSignal = impl->mShutdownMarker;
|
|
|
|
// Wait for played buffer
|
|
WaitForSingleObject(impl->mDoneSignal, 500);
|
|
|
|
// Iterate buffers to find played
|
|
for (i = 0; i < AUDIO_SPK_BUFFER_COUNT; i++)
|
|
{
|
|
index = (impl->mBufferIndex + i) % AUDIO_SPK_BUFFER_COUNT;
|
|
Buffer& buffer = impl->mBufferList[index];
|
|
if (!(buffer.mHeader->dwFlags & WHDR_DONE))
|
|
break;
|
|
|
|
buffer.unprepare(impl->mDevice);
|
|
if (!exitSignal)
|
|
{
|
|
bool useAEC = true;
|
|
if (impl->connection())
|
|
impl->connection()->onSpkData(Format(), buffer.mData, AUDIO_SPK_BUFFER_SIZE);
|
|
else
|
|
memset(buffer.mData, 0, AUDIO_SPK_BUFFER_SIZE);
|
|
|
|
buffer.prepare(impl->mDevice);
|
|
buffer.write(impl->mDevice);
|
|
}
|
|
else
|
|
exitCount++;
|
|
}
|
|
impl->mBufferIndex = (impl->mBufferIndex + i) % AUDIO_SPK_BUFFER_COUNT;
|
|
} while (!exitSignal || exitCount < AUDIO_SPK_BUFFER_COUNT);
|
|
impl->closeDevice();
|
|
}
|
|
|
|
HWAVEOUT WmmeOutputDevice::handle()
|
|
{
|
|
return mDevice;
|
|
}
|
|
|
|
unsigned WmmeOutputDevice::playedTime()
|
|
{
|
|
if (!mDevice)
|
|
return 0;
|
|
unsigned result = 0;
|
|
|
|
MMTIME mmt;
|
|
memset(&mmt, 0, sizeof(mmt));
|
|
mmt.wType = TIME_SAMPLES;
|
|
MMRESULT rescode = waveOutGetPosition(mDevice, &mmt, sizeof(mmt));
|
|
if (rescode != MMSYSERR_NOERROR || mmt.wType != TIME_SAMPLES)
|
|
closeDevice();
|
|
else
|
|
{
|
|
if (mmt.u.ms < mPlayedTime)
|
|
result = 0;
|
|
else
|
|
{
|
|
result = mmt.u.ms - mPlayedTime;
|
|
mPlayedTime = mmt.u.ms - result % 8;
|
|
}
|
|
}
|
|
|
|
return result / 8;
|
|
}
|
|
|
|
void WmmeOutputDevice::setFakeMode(bool fakemode)
|
|
{
|
|
closeDevice();
|
|
}
|
|
|
|
bool WmmeOutputDevice::fakeMode()
|
|
{
|
|
return mFailed;
|
|
}
|
|
|
|
|
|
bool WmmeOutputDevice::closing()
|
|
{
|
|
return mClosing;
|
|
}
|
|
|
|
void CALLBACK WmmeOutputDevice::callbackProc(HWAVEOUT hwo, UINT msg, DWORD_PTR dwInstance, DWORD_PTR dwParam1,
|
|
DWORD_PTR dwParam2)
|
|
{
|
|
WmmeOutputDevice* impl;
|
|
|
|
if (msg == WOM_DONE)
|
|
{
|
|
impl = (WmmeOutputDevice*)dwInstance;
|
|
InterlockedIncrement(&impl->mPlayedCount);
|
|
SetEvent(impl->mDoneSignal);
|
|
}
|
|
}
|
|
|
|
#endif
|