#ifdef USE_WMF
#include <MMDeviceAPI.h>
#include <AudioClient.h>
#include <AudioPolicy.h>
#include <avrt.h>
#include <functiondiscoverykeys.h>
const bool DisableMMCSS = false;
String GetDeviceName(IMMDeviceCollection *DeviceCollection, UINT DeviceIndex);
bool PickDevice(IMMDevice **DeviceToUse, bool *IsDefaultDevice, ERole *DefaultDeviceRole);
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
class CWASAPICapture : public IAudioSessionEvents, IMMNotificationClient
{
public:
CWASAPICapture(IMMDevice *Endpoint, VideoCompressor *Compressor);
bool Initialize(UINT32 EngineLatency);
void Shutdown();
bool Start(BYTE *CaptureBuffer, size_t BufferSize);
void Stop();
WORD ChannelCount() { return _MixFormat->Format.nChannels; }
UINT32 SamplesPerSecond() { return _MixFormat->Format.nSamplesPerSec; }
UINT32 BytesPerSample() { return _MixFormat->Format.wBitsPerSample / 8; }
size_t FrameSize() { return _FrameSize; }
WAVEFORMATEX *MixFormat() { return &(_MixFormat->Format); }
size_t BytesCaptured() { return _CurrentCaptureIndex; }
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
private:
~CWASAPICapture(void);
LONG _RefCount;
IMMDevice* _Endpoint;
IAudioClient* _AudioClient;
IAudioCaptureClient* _CaptureClient;
HANDLE _CaptureThread;
HANDLE _ShutdownEvent;
WAVEFORMATEXTENSIBLE* _MixFormat;
size_t _FrameSize;
UINT32 _BufferSize;
BYTE* _CaptureBuffer;
size_t _CaptureBufferSize;
size_t _CurrentCaptureIndex;
VideoCompressor* _Compressor;
static DWORD __stdcall WASAPICaptureThread(LPVOID Context);
DWORD CWASAPICapture::DoCaptureThread();
bool _EnableStreamSwitch;
HANDLE _StreamSwitchEvent; HANDLE _StreamSwitchCompleteEvent; IAudioSessionControl* _AudioSessionControl;
IMMDeviceEnumerator* _DeviceEnumerator;
LONG _EngineLatencyInMS;
bool _InStreamSwitch;
STDMETHOD(OnDisplayNameChanged) (LPCWSTR , LPCGUID ) { return S_OK; };
STDMETHOD(OnIconPathChanged) (LPCWSTR , LPCGUID ) { return S_OK; };
STDMETHOD(OnSimpleVolumeChanged) (float , BOOL , LPCGUID ) { return S_OK; }
STDMETHOD(OnChannelVolumeChanged) (DWORD , float [], DWORD , LPCGUID ) { return S_OK; };
STDMETHOD(OnGroupingParamChanged) (LPCGUID , LPCGUID ) {return S_OK; };
STDMETHOD(OnStateChanged) (AudioSessionState ) { return S_OK; };
STDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason);
STDMETHOD(OnDeviceStateChanged) (LPCWSTR , DWORD ) { return S_OK; }
STDMETHOD(OnDeviceAdded) (LPCWSTR ) { return S_OK; };
STDMETHOD(OnDeviceRemoved) (LPCWSTR ) { return S_OK; };
STDMETHOD(OnDefaultDeviceChanged) (EDataFlow Flow, ERole Role, LPCWSTR NewDefaultDeviceId);
STDMETHOD(OnPropertyValueChanged) (LPCWSTR , const PROPERTYKEY ){return S_OK; };
STDMETHOD(QueryInterface)(REFIID iid, void **pvObject);
bool InitializeAudioEngine();
bool LoadFormat();
};
CWASAPICapture::CWASAPICapture(IMMDevice *Endpoint, VideoCompressor *Compressor) :
_RefCount(1),
_Endpoint(Endpoint),
_AudioClient(NULL),
_CaptureClient(NULL),
_CaptureThread(NULL),
_ShutdownEvent(NULL),
_MixFormat(NULL),
_CurrentCaptureIndex(0),
_StreamSwitchEvent(NULL),
_StreamSwitchCompleteEvent(NULL),
_AudioSessionControl(NULL),
_DeviceEnumerator(NULL),
_Compressor(Compressor),
_InStreamSwitch(false)
{
_Endpoint->AddRef(); }
CWASAPICapture::~CWASAPICapture(void)
{
}
bool CWASAPICapture::InitializeAudioEngine()
{
HRESULT hr = _AudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_NOPERSIST, _EngineLatencyInMS*10000, 0, MixFormat(), NULL);
PersistentAssert(SUCCEEDED(hr), "_AudioClient->Initialize failed");
hr = _AudioClient->GetBufferSize(&_BufferSize);
PersistentAssert(SUCCEEDED(hr), "_AudioClient->GetBufferSize failed");
hr = _AudioClient->GetService(IID_PPV_ARGS(&_CaptureClient));
PersistentAssert(SUCCEEDED(hr), "_AudioClient->GetService failed");
return true;
}
bool CWASAPICapture::LoadFormat()
{
HRESULT hr = _AudioClient->GetMixFormat((WAVEFORMATEX**)&_MixFormat);
PersistentAssert(SUCCEEDED(hr), "_AudioClient->GetMixFormat failed");
_FrameSize = (_MixFormat->Format.wBitsPerSample / 8) * _MixFormat->Format.nChannels;
return true;
}
bool CWASAPICapture::Initialize(UINT32 EngineLatency)
{
_ShutdownEvent = CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
PersistentAssert(_ShutdownEvent != NULL, "CreateEventEx failed");
_StreamSwitchEvent = CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
PersistentAssert(_StreamSwitchEvent != NULL, "CreateEventEx failed");
HRESULT hr = _Endpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&_AudioClient));
PersistentAssert(SUCCEEDED(hr), "_Endpoint->Activate failed");
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_DeviceEnumerator));
PersistentAssert(SUCCEEDED(hr), "CoCreateInstance failed");
LoadFormat();
_EngineLatencyInMS = EngineLatency;
InitializeAudioEngine();
return true;
}
void CWASAPICapture::Shutdown()
{
if (_CaptureThread)
{
SetEvent(_ShutdownEvent);
WaitForSingleObject(_CaptureThread, INFINITE);
CloseHandle(_CaptureThread);
_CaptureThread = NULL;
}
if (_ShutdownEvent)
{
CloseHandle(_ShutdownEvent);
_ShutdownEvent = NULL;
}
if (_StreamSwitchEvent)
{
CloseHandle(_StreamSwitchEvent);
_StreamSwitchEvent = NULL;
}
SafeRelease(&_Endpoint);
SafeRelease(&_AudioClient);
SafeRelease(&_CaptureClient);
if (_MixFormat)
{
CoTaskMemFree(_MixFormat);
_MixFormat = NULL;
}
}
bool CWASAPICapture::Start(BYTE *CaptureBuffer, size_t CaptureBufferSize)
{
HRESULT hr;
_CaptureBuffer = CaptureBuffer;
_CaptureBufferSize = CaptureBufferSize;
_CaptureThread = CreateThread(NULL, 0, WASAPICaptureThread, this, 0, NULL);
PersistentAssert(_CaptureThread != NULL, "CreateThread failed");
hr = _AudioClient->Start();
PersistentAssert(SUCCEEDED(hr), "_AudioClient->Start failed");
return true;
}
void CWASAPICapture::Stop()
{
HRESULT hr;
if (_ShutdownEvent)
{
SetEvent(_ShutdownEvent);
}
hr = _AudioClient->Stop();
PersistentAssert(SUCCEEDED(hr), "_AudioClient->Stop failed");
if (_CaptureThread)
{
WaitForSingleObject(_CaptureThread, INFINITE);
CloseHandle(_CaptureThread);
_CaptureThread = NULL;
}
}
DWORD CWASAPICapture::WASAPICaptureThread(LPVOID Context)
{
CWASAPICapture *capturer = static_cast<CWASAPICapture *>(Context);
return capturer->DoCaptureThread();
}
DWORD CWASAPICapture::DoCaptureThread()
{
bool stillPlaying = true;
HANDLE waitArray[2] = {_ShutdownEvent, _StreamSwitchEvent};
HANDLE mmcssHandle = NULL;
DWORD mmcssTaskIndex = 0;
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
PersistentAssert(SUCCEEDED(hr), "CoInitializeEx failed");
if (!DisableMMCSS)
{
mmcssHandle = AvSetMmThreadCharacteristics("Audio", &mmcssTaskIndex);
PersistentAssert(mmcssHandle != NULL, "AvSetMmThreadCharacteristics failed");
}
while (stillPlaying)
{
HRESULT hr;
DWORD waitResult = WaitForMultipleObjects(2, waitArray, FALSE, _EngineLatencyInMS / 2);
switch (waitResult)
{
case WAIT_OBJECT_0 + 0: stillPlaying = false; break;
case WAIT_OBJECT_0 + 1: PersistentSignalError("StreamSwitch event unexpected");
stillPlaying = false;
break;
case WAIT_TIMEOUT: BYTE *pData;
UINT32 framesAvailable;
DWORD flags;
UINT64 CaptureStartTime;
hr = _CaptureClient->GetBuffer(&pData, &framesAvailable, &flags, NULL, &CaptureStartTime);
if (SUCCEEDED(hr))
{
UINT32 framesToCopy = min(framesAvailable, static_cast<UINT32>((_CaptureBufferSize - _CurrentCaptureIndex) / _FrameSize));
const UINT BytesToCopy = framesToCopy * _FrameSize;
if (framesToCopy != 0)
{
if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
{
ZeroMemory(&_CaptureBuffer[_CurrentCaptureIndex], BytesToCopy);
}
else
{
CopyMemory(&_CaptureBuffer[_CurrentCaptureIndex], pData, BytesToCopy);
}
if(_Compressor == NULL)
{
_CurrentCaptureIndex += BytesToCopy;
}
}
hr = _CaptureClient->ReleaseBuffer(framesAvailable);
PersistentAssert(SUCCEEDED(hr), "_CaptureClient->ReleaseBuffer failed");
if(_Compressor && framesToCopy != 0)
{
_Compressor->AudioSample32Bit2Channel((float *)_CaptureBuffer, framesToCopy, CaptureStartTime);
}
}
break;
}
}
if (!DisableMMCSS)
{
AvRevertMmThreadCharacteristics(mmcssHandle);
}
CoUninitialize();
return 0;
}
HRESULT CWASAPICapture::OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason)
{
if (DisconnectReason == DisconnectReasonDeviceRemoval)
{
_InStreamSwitch = true;
SetEvent(_StreamSwitchEvent);
}
if (DisconnectReason == DisconnectReasonFormatChanged)
{
_InStreamSwitch = true;
SetEvent(_StreamSwitchEvent);
SetEvent(_StreamSwitchCompleteEvent);
}
return S_OK;
}
HRESULT CWASAPICapture::OnDefaultDeviceChanged(EDataFlow Flow, ERole Role, LPCWSTR )
{
if (Flow == eCapture)
{
if (!_InStreamSwitch)
{
_InStreamSwitch = true;
SetEvent(_StreamSwitchEvent);
}
SetEvent(_StreamSwitchCompleteEvent);
}
return S_OK;
}
HRESULT CWASAPICapture::QueryInterface(REFIID Iid, void **Object)
{
if (Object == NULL)
{
return E_POINTER;
}
*Object = NULL;
if (Iid == IID_IUnknown)
{
*Object = static_cast<IUnknown *>(static_cast<IAudioSessionEvents *>(this));
AddRef();
}
else if (Iid == __uuidof(IMMNotificationClient))
{
*Object = static_cast<IMMNotificationClient *>(this);
AddRef();
}
else if (Iid == __uuidof(IAudioSessionEvents))
{
*Object = static_cast<IAudioSessionEvents *>(this);
AddRef();
}
else
{
return E_NOINTERFACE;
}
return S_OK;
}
ULONG CWASAPICapture::AddRef()
{
return InterlockedIncrement(&_RefCount);
}
ULONG CWASAPICapture::Release()
{
ULONG returnValue = InterlockedDecrement(&_RefCount);
if (returnValue == 0)
{
delete this;
}
return returnValue;
}
String GetDeviceName(IMMDeviceCollection *DeviceCollection, UINT DeviceIndex)
{
IMMDevice *device;
LPWSTR deviceId;
HRESULT hr;
hr = DeviceCollection->Item(DeviceIndex, &device);
PersistentAssert(SUCCEEDED(hr), "DeviceCollection->Item failed");
hr = device->GetId(&deviceId);
PersistentAssert(SUCCEEDED(hr), "device->GetId failed");
IPropertyStore *propertyStore;
hr = device->OpenPropertyStore(STGM_READ, &propertyStore);
SafeRelease(&device);
PersistentAssert(SUCCEEDED(hr), "device->OpenPropertyStore failed");
PROPVARIANT friendlyName;
PropVariantInit(&friendlyName);
hr = propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
SafeRelease(&propertyStore);
PersistentAssert(SUCCEEDED(hr), "propertyStore->GetValue failed");
String Result = String(UnicodeString(friendlyName.pwszVal));
PropVariantClear(&friendlyName);
CoTaskMemFree(deviceId);
return Result;
}
bool PickDevice(IMMDevice **DeviceToUse, UINT AudioDeviceIndex)
{
IMMDeviceEnumerator *deviceEnumerator = NULL;
IMMDeviceCollection *deviceCollection = NULL;
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
PersistentAssert(SUCCEEDED(hr), "CoCreateInstance failed");
IMMDevice *device = NULL;
hr = deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection);
PersistentAssert(SUCCEEDED(hr), "deviceEnumerator->EnumAudioEndpoints failed");
UINT deviceCount;
hr = deviceCollection->GetCount(&deviceCount);
PersistentAssert(SUCCEEDED(hr), "deviceCollection->GetCount failed");
for (UINT DeviceIndex = 0 ; DeviceIndex < deviceCount; DeviceIndex++)
{
String deviceName = GetDeviceName(deviceCollection, DeviceIndex);
}
int deviceIndex = 0;
if(AudioDeviceIndex < deviceCount)
{
deviceIndex = AudioDeviceIndex;
}
hr = deviceCollection->Item(deviceIndex, DeviceToUse);
PersistentAssert(DeviceToUse != NULL && SUCCEEDED(hr), "deviceCollection->Item failed");
SafeRelease(&deviceCollection);
SafeRelease(&deviceEnumerator);
return true;
}
struct WAVEHEADER
{
DWORD dwRiff; DWORD dwSize; DWORD dwWave; DWORD dwFmt; DWORD dwFmtSize; };
const BYTE WaveHeader[] =
{
'R', 'I', 'F', 'F', 0x00, 0x00, 0x00, 0x00, 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ', 0x00, 0x00, 0x00, 0x00
};
const BYTE WaveData[] = { 'd', 'a', 't', 'a'};
bool WriteWaveFile(HANDLE FileHandle, const BYTE *Buffer, const size_t BufferSize, const WAVEFORMATEX *WaveFormat)
{
DWORD waveFileSize = sizeof(WAVEHEADER) + sizeof(WAVEFORMATEX) + WaveFormat->cbSize + sizeof(WaveData) + sizeof(DWORD) + static_cast<DWORD>(BufferSize);
BYTE *waveFileData = new (std::nothrow) BYTE[waveFileSize];
BYTE *waveFilePointer = waveFileData;
WAVEHEADER *waveHeader = reinterpret_cast<WAVEHEADER *>(waveFileData);
if (waveFileData == NULL)
{
printf("Unable to allocate %d bytes to hold output wave data\n", waveFileSize);
return false;
}
CopyMemory(waveFilePointer, WaveHeader, sizeof(WaveHeader));
waveFilePointer += sizeof(WaveHeader);
waveHeader->dwSize = waveFileSize - (2 * sizeof(DWORD));
waveHeader->dwFmtSize = sizeof(WAVEFORMATEX) + WaveFormat->cbSize;
CopyMemory(waveFilePointer, WaveFormat, sizeof(WAVEFORMATEX) + WaveFormat->cbSize);
waveFilePointer += sizeof(WAVEFORMATEX) + WaveFormat->cbSize;
CopyMemory(waveFilePointer, WaveData, sizeof(WaveData));
waveFilePointer += sizeof(WaveData);
*(reinterpret_cast<DWORD *>(waveFilePointer)) = static_cast<DWORD>(BufferSize);
waveFilePointer += sizeof(DWORD);
CopyMemory(waveFilePointer, Buffer, BufferSize);
DWORD bytesWritten;
if (!WriteFile(FileHandle, waveFileData, waveFileSize, &bytesWritten, NULL))
{
printf("Unable to write wave file: %d\n", GetLastError());
delete []waveFileData;
return false;
}
if (bytesWritten != waveFileSize)
{
printf("Failed to write entire wave file\n");
delete []waveFileData;
return false;
}
delete []waveFileData;
return true;
}
void SaveWaveData(BYTE *CaptureBuffer, size_t BufferSize, const WAVEFORMATEX *WaveFormat, const String &Filename)
{
HANDLE waveHandle = CreateFile(Filename.CString(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (waveHandle != INVALID_HANDLE_VALUE)
{
bool Success = WriteWaveFile(waveHandle, CaptureBuffer, BufferSize, WaveFormat);
PersistentAssert(Success, "WriteWaveFile failed");
CloseHandle(waveHandle);
}
}
bool AudioCapture::StartCapture(VideoCompressor *Compressor, UINT AudioDeviceIndex)
{
_Compressor = Compressor;
_Filename.FreeMemory();
_CaptureBuffer.FreeMemory();
return StartCaptureInternal(AudioDeviceIndex);
}
bool AudioCapture::StartCapture(const String &Filename, UINT AudioDeviceIndex)
{
_Compressor = NULL;
_Filename = Filename;
_CaptureBuffer.FreeMemory();
return StartCaptureInternal(AudioDeviceIndex);
}
void AudioCapture::StopCapture()
{
_Capturer->Stop();
if(_Compressor == NULL && _Filename.Length() > 0 && _CaptureBuffer.Length() > 0)
{
Console::WriteLine(String("Saving WAV file: ") + _Filename);
SaveWaveData(_CaptureBuffer.CArray(), _Capturer->BytesCaptured(), _Capturer->MixFormat(), _Filename);
}
_Capturer->Shutdown();
SafeRelease(&_Capturer);
SafeRelease(&_CaptureDevice);
}
bool AudioCapture::StartCaptureInternal(UINT AudioDeviceIndex)
{
PersistentAssert(_CaptureDevice == NULL && _Capturer == NULL, "StartCapture called without StopCapture");
const int TargetLatency = 20;
int TargetDurationInSec = 10;
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
bool Success = PickDevice(&_CaptureDevice, AudioDeviceIndex);
PersistentAssert(Success, "PickDevice failed");
_Capturer = new (std::nothrow) CWASAPICapture(_CaptureDevice, _Compressor);
PersistentAssert(_Capturer != NULL, "Allocate CWASAPICapture failed");
if (_Capturer->Initialize(TargetLatency))
{
size_t captureBufferSize = _Capturer->SamplesPerSecond() * TargetDurationInSec * _Capturer->FrameSize();
_CaptureBuffer.Allocate(captureBufferSize);
bool Success = _Capturer->Start(_CaptureBuffer.CArray(), captureBufferSize);
PersistentAssert(Success, "_Capturer->Start failed");
}
return true;
}
#endif